文章目录
- 前置知识点
- 会话技术Conversation
- 客户端技术Cookie
- Cookie的格式
- Cookie的优缺点
- 构造Cookie信息
- 通过浏览器构造Cookie
- 通过Postman构造Cookie
- 通过服务器构造Cookie
- 获取Cookie信息
- Cookie中的信息
- Path
- Domain
- MaxAge
- 案例(cookie相关)
- 服务器技术Session
- 提供Session
- 获取Session
- 使用Session
- Session的生命周期
- 常见问题
- 案例(重构登录案例,并且增加注销功能)
- Cookie和Session的区别与联系
- 数据共享
前置知识点
- 响应头的设置
response.setHeader(String,String)
- Postman中设置请求头
- Header
- URL编码
- 浏览器能够完成编解码的工作
- 比如我们发送一个Get请求(通过浏览器地址栏),其中的请求参数有中文,这个中文浏览器会自动进行URL编码 → Servlet中使用request获得请求参数的值,这个值是中文,说明request已经解码
eg:
public class URLEncodingDecodingExecution {
public static void main(String[] args) throws UnsupportedEncodingException {
// 提供编码的方法
String encode = URLEncoder.encode("张三", "utf-8");
System.out.println(encode); //%E5%BC%A0%E4%B8%89
// 提供解码的方法
String decode = URLDecoder.decode("%E5%BC%A0%E4%B8%89", "utf-8");
System.out.println(decode); // 张三
}
}
会话技术Conversation
- 同一个客户端向服务器中发送的多个请求,需要信息共享
- 在做服务器开发过程中,客户端和服务器之间,会有请求报文和响应报文
- 客户端给服务器发送请求:请求报文
- 服务器给客户端发送响应:响应报文
- HTTP协议的无状态性,会导致服务器无法区分信息来自于哪个客户端
- 无状态性:协议对于交互性场景没有记忆能力
- eg:
http://localhost:8080/demo1/user/list/user?userid=25;
- 会产生弊端:
- 用户信息不安全
- 客户端和浏览器每一次发送请求的时候都需要携带请求参数比较繁琐
- 引入会话技术,最重要的事情就是让服务器知道客户端是谁
- 客户端直接携带确切的信息,这个就是客户端技术
- 客户端技术:
Cookie
- eg:请求报文中携带对应的信息
username=zs
- 客户端技术:
- 如果是通过客户端提供的编号,进而在服务器中进一步获得信息,那么这个就是服务器技术
- 服务器技术:
Session
- eg:服务器中存储
编号30012:zs
- 服务器技术:
- 服务器会话技术,是在客户端会话技术基础上的
客户端技术Cookie
- 携带信息:客户端(浏览器)在向服务器发起请求的时候直接携带了信息,这些信息是通过请求头中一个特殊的请求头(特殊的请求头叫Cookie)携带的
Cookie的格式
- Cookie:
key1=value1;key2=value2
- 携带的是键值对信息,携带的键值对信息都是字符串信息
- 可以携带多组键值对信息,如果携带多组,中间使用分号分隔开
Cookie的优缺点
- 优点:小巧、减轻了服务器压力、可以很轻松的实现多台主机、多个应用下的资源共享
- 缺点:存储容量有限制 4kb、数据类型有限制、只可以存储一些非敏感数据
构造Cookie信息
通过浏览器构造Cookie
- 打开开发者工具,快捷键F12
- 找应用程序(Application)
- 应用程序里找存储(Storage)里的Cookie
- 可以通过Fiddler获取Cookie值
通过Postman构造Cookie
eg:
-
通过Postman进行创建
-
通过Fiddler进行抓取
通过服务器构造Cookie
客户端 → 服务器,请求
服务器 → 客户端,响应(并且携带响应头:set-cookie:key=value
)
客户端 → 服务器,请求(并且携带请求头:cookie:key=value
)
客户端 → 服务器,请求(并且携带请求头:cookie:key=value
)
eg:
/**
* 发送请求
* http://localhost:8080/demo1/cookie/set?username=zs
*/
@WebServlet("/cookie/set")
public class CookieSetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
response.setHeader("set-cookie","username=" + username);
response.getWriter().println("cookie set finish, please check cookie");
}
}
获取Cookie信息
- Request中提供了直接获得Cookie的方法
Cookie[] cookies = request.getCookies();
- 单个Cookie,我们先获得其键值对信息
- 键:
cookie.getName()
- 值:
cookie.getValue()
- 键:
eg:
/**
* 获取所有的cookie,然后进行打印
*/
@WebServlet("/cookie/fetch")
public class CookieFetchServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// String cookie = request.getHeader("cookie");
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println(name + " : " + value);
}
/**
* username : ls
* Pycharm-89c7dd8b : 47f9fb69-6795-467d-875b-0a44c63e1e97
* Idea-75c3c9a : b19ee743-c677-4bab-b7bd-673c42a25ce0
*/
}
}
Cookie中的信息
Cookie这个实例中的封装信息:
信息 | 方法 | 说明 |
---|---|---|
name | 有参构造方法 | 核心值 |
value | 有参构造方法 | 核心值 |
Path | setPath(String) | 设置Cookie的有效URI |
Domain | setDomain(String) | 域名,做Cookie的共享 |
MaxAge | setMaxAge(int) | cookie的有效时间、设置过期时间,单位是秒,如果超过这个时间Cookie就会过期 |
Path
- 客户端发起的请求,是某个path的话,客户端在发起请求的时候才会携带这个cookie信息
- 这个Path就是设置cookie的执行路径
cookie.setPath(String path)
eg:
@WebServlet("/cookie/path")
public class CookiePathServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response)
throws ServletException, IOException {
// response.setHeader("set-cookie","xxxx=" + xxxx);
Cookie cookie = new Cookie("username", "ls");
// 默认Path:URI去除掉最后一级 uri=demo1/cookie/path
// ---> path=demo1/cookie
// 手动构造Path
cookie.setPath("/demo1"); // 则/demo1目录下的cookie都有username=ls
response.addCookie(cookie);
}
}
Domain
- 设置域名或ip,用来说不同域名下Cookie共享
- 如果设置了Cookie的父域名,子域名下的请求可以共享父域名下的Cookie
- eg:
- 父域名:
https://www.jd.com
- 子域名:
https://www.miaosha.jd.com
- 父域名:
- 不能设置和当前域名无关的domain,比如访问localhost时候设置一个ccc.com这样的domain是不可以的,否则浏览器会直接无视
eg:
@WebServlet("/cookie/domain")
public class CookieDomainServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie = new Cookie("username", "ww");
cookie.setDomain("ccc.com");
response.addCookie(cookie);
}
}
MaxAge
- 如果没有设置,则默认情况下存在于浏览器的内存中。关闭浏览器,则cookie信息失效
- 设置一个
maxAge=正数的时间
,表示会在硬盘上面存活多少秒
eg:
@WebServlet("/cookie/maxage")
public class CookieMaxAgeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie = new Cookie("userid", "251");
cookie.setMaxAge(5); // 设置一个过期时间,如果没有设置,则浏览器关闭时会被清楚
// 这个只有浏览器知道
response.addCookie(cookie);
}
}
案例(cookie相关)
在访问一个请求的时候,输出上一次访问的时间
思路:
- 访问该请求的时候,生成时间,放入到cookie中
- 访问该请求的时候,从cookie中取出这个时间,并且使用response做响应
eg:
@WebServlet("/last/access")
public class LastAccessServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取cookie中的上一次访问时间
// 假设设定了cookie的name的last
Cookie[] cookies = request.getCookies();
String lastDateString = null;
if (cookies != null & cookies.length != 0) {
for (Cookie cookie : cookies) {
if ("last".equals(cookie.getName())) {
lastDateString = cookie.getValue();
}
}
}
if(lastDateString != null) {
String lastDateStringDecode = URLDecoder.decode(lastDateString, "utf-8");
response.getWriter().println(lastDateStringDecode);
}
// 向浏览器的cookie中做当前时间的设置
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowDate = simpleDateFormat.format(new Date());
String nowDateEncode = URLEncoder.encode(nowDate, "utf-8");
response.addCookie(new Cookie("last", nowDateEncode));
}
}
服务器技术Session
- Session相当于每个用户存在服务器的保险柜,保险柜里可以存一些数据,这些数据也可以是敏感数据;
- 要获得保险柜要带着钥匙,如果钥匙丢了,就打不开这个保险柜
提供Session
- Session不需要专门去提供,当我们获取Session的时候,其实就提供了Session给客户端
- 在服务端获取Session的时候,其实会提供一个响应头
set-cookie
JSESSIONID
这样的一个key,其实就是保险柜的钥匙
获取Session
- 获得Session可以使用Request提供的getSession方法
getSession()
- 如果还没有创建Session,那么就创建一个Session
- 如果已经有了Session,那么就返回这个Session
- 当前这个客户端或用户第一个调用getSession方法的时候创建了Session
getSession(boolean create)
- 如果create的值为true则同上
- 如果create的值为false,那么如果有了Session则返回Session对象,如果没有则返回null,就不会创建Session
实际上我们使用的无参方法更多一些
eg:
@WebServlet("/session/get")
public class SessionGetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 根据JSESSIONID,不需要我们手动取获取,内部已经封装好了
// 获取当前用户地session,如果当前用户没有session,则新建一个session
// Set-Cookie: JSESSIONID=DA43DEBCD0DFF4BBFC4371558FE8C7B4
HttpSession session = request.getSession();
// 如果boolean类型参数为true,和无参的getSession方法是一样的
// HttpSession session = request.getSession(true);
// 如果为false,且当前用户有session,则返回,如果没有session,则返回空
// HttpSession session = request.getSession(false);
}
}
使用Session
- Session其实就像一个保险柜 →
key-value
形式的保险柜- key:
String
- value:
Object
- 存储:
setAttribute(String,Object)
- 获取:
getAttribute(String)
- key:
eg:
WebServlet("/session/save")
public class SessionSaveServlet extends HttpServlet {
// localhost:8080/demo2/session/save?username=zs
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 传入一个用户名 -> 找到id,将id存在session里面
String username = request.getParameter("username"); // 根据用户名可以找到该用户的id信息
// 假定这个id是222
Integer id = 222;
HttpSession session = request.getSession();
session.setAttribute("id",id);
session.setAttribute("username", username);
}
}
@WebServlet("/session/fetch")
public class SessionFetchServlet extends HttpServlet {
// localhost:8080/demo2/session/fetch
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 从session中获取username和id信息
HttpSession session = request.getSession();
Integer id = (Integer) session.getAttribute("id");
String username = (String) session.getAttribute("username");
System.out.println(username + " : " + id); // zs : 222
}
}
Session的生命周期
- 对象生命周期:
- 对象的创建:
request.getSession()
- 对象的销毁:关闭服务器、卸载应用
- 对象的创建:
- 数据的生命周期:
- 数据的产生:
session域
- 数据的销毁:对象的销毁不会导致数据的销毁
- 数据的销毁只有以下两种可能性:
- session有效期到达(默认是30min)
- 主动调用
session.invalidate()
方法-----用在注销的场景下
- 数据的销毁只有以下两种可能性:
- 数据的产生:
常见问题
- 关闭浏览器,Session是否被销毁?
- 并没有
- 只不过当前浏览器发起请求的时候,通常没有继续携带之前的
JSESSIONID
,但如果你仍然携带之前的JSESSIONID
,那么仍然可以获取数据
- 服务器关闭,Session发生了什么?
- Session对象会被销毁,并且会被序列化(序列化:把数据写到服务器里)
- 当服务器重启时,会被反序列化(反序列化:把数据读出),重新加载到内容。
- 当时要注意,重启前后的Session对象并不是同一个,但是JSESSIONID是同一个值
- Session失效,原因会是什么?
- Session失效就是从Session中获取不到其存储的信息,则可以认为Session失效
- 跨域请求Session不能共享
- 跨域:多个应用之间的ip或端口号不同
- 比如:前端地址
localhost:9527
与后端服务器地址localhost:8080
- 比如:前端地址
- 每发起一次请求,就会新建一个新的Session
- 跨域:多个应用之间的ip或端口号不同
- 过期或手动设置
- 请求携带的JSESSIONID变了
- 跨域请求Session不能共享
- Session失效就是从Session中获取不到其存储的信息,则可以认为Session失效
- session底层是依赖于cookie的,但是如果浏览器禁用了cookie,那么session还可以使用吗?
- 还可以。但是必须采用一种URL重写的方式。此时session的编号会附着在地址栏的后面,以
;
形式来进行分割 - eg:
localhost:8080/demo2/session/fetch;JSESSIONID=aaaabc2849ea
- 还可以。但是必须采用一种URL重写的方式。此时session的编号会附着在地址栏的后面,以
案例(重构登录案例,并且增加注销功能)
bean
目录
@Data
public class Order {
private Integer id;
private String name;
private Double price;
private Integer userId;
}
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private Date birthday;
private Date createdate;
private String mobile;
}
mapper
目录
public interface OrderMapper {
List<Order> selectByUserId(Integer id);
}
public interface UserMapper {
List<User> selectByUserNameAndPassword
(@Param("username") String username, @Param("password") String password);
User selectByPrimaryKey(Integer id);
}
<mapper namespace="com.coo1heisenberg.demo3.mapper.OrderMapper">
<select id="selectByUserId" resultType="com.coo1heisenberg.demo3.bean.Order">
select id, name, price, user_id as userId from test_product
<where>
user_id = #{user_id}
</where>
</select>
</mapper>
<mapper namespace="com.coo1heisenberg.demo3.mapper.UserMapper">
<select id="selectByUserNameAndPassword" resultType="com.coo1heisenberg.demo3.bean.User">
select id, username, password, age, birthday, createDate, mobile from test_user
<where>
username = #{username} and password = #{password}
</where>
</select>
<select id="selectByPrimaryKey" resultType="com.coo1heisenberg.demo3.bean.User">
select id, username, password, age, birthday, createDate, mobile from test_user
<where>
id = #{id}
</where>
</select>
</mapper>
servlet
目录
/**
* 提供一个list方法,登录成功才允许查看当前用户的订单信息,否则让其先登录
*/
@WebServlet("/order/*")
public class OrderServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
process(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
process(request, response);
}
private void process(HttpServletRequest request, HttpServletResponse response) {
String requestURI = request.getRequestURI();
String operation = requestURI.substring(requestURI.lastIndexOf("/") + 1);
switch (operation) {
case "list":
list(request, response);
break;
}
}
@SneakyThrows
private void list(HttpServletRequest request, HttpServletResponse response) {
// 首先要保证当前用户已经登陆 -> 登录成功之后,session里才有用户信息
// -> 可以判断session是否有用户信息
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
response.setContentType("text/html;charset=utf-8");
if (user == null) {
// 登录失败
response.getWriter().println("登录失败,即将跳转登录页面");
response.setHeader("refresh", "2;url=/demo3/login.html");
} else {
// 已经登录成功
Integer userId = user.getId();
OrderMapper orderMapper = MybatisUtil.getSqlSession().getMapper(OrderMapper.class);
List<Order> orders = orderMapper.selectByUserId(userId);
response.getWriter().println(orders);
}
}
}
@WebServlet("/user/*")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
process(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
process(req, resp);
}
private void process(HttpServletRequest request, HttpServletResponse response) {
String requestURI = request.getRequestURI();
String operation = null;
operation = requestURI.substring(requestURI.lastIndexOf("/") + 1);
switch (operation) {
case "login":
login(request, response);
break;
case "info":
info(request, response);
break;
case "logout":
logout(request,response);
break;
}
}
@SneakyThrows
private void logout(HttpServletRequest request, HttpServletResponse response) {
// 做注销功能 localhost:8080/demo3/user/logout
HttpSession session = request.getSession();
session.invalidate();
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("已经注销");
}
@SneakyThrows
private void info(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
Object user = session.getAttribute("user");
response.setContentType("text/html;charset=utf-8");
if(user == null) {
// 意味着之前未登录成功
response.getWriter().println("还未登录,不能查看用户信息,请先登录,即将跳转");
response.setHeader("refresh","2;url=/demo3/login.html");
} else {
// user != null
// 意味着已经登录成功
response.getWriter().println(user);
}
}
@SneakyThrows
private void login(HttpServletRequest request, HttpServletResponse response) {
// 1. 首先获得username和password
String username = request.getParameter("username");
String password = request.getParameter("password");
// 2. 查询user记录
UserMapper userMapper = MybatisUtil.getSqlSession().getMapper(UserMapper.class);
List<User> users = userMapper.selectByUserNameAndPassword(username, password);
// 3. 根据user记录判断登录状态
response.setContentType("text/html;charset=utf-8");
// 4. 判断登录状态 user的list是否为空
if (users == null || users.size() == 0) {
// 如果登陆失败(list == null || list.size() == 0)
// 刷新到refresh --> 2;url=/demo2/login.html
// 响应登录失败信息
response.getWriter().println("登录失败,即将跳转登录页面");
response.setHeader("refresh", "2;url=/demo3/login.html");
} else {
// 如果登录成功(list.size > 0)
// 登录成功之后,将用户信息放在服务器Session里
HttpSession session = request.getSession();
session.setAttribute("user",users.get(0));
response.getWriter().println("登录成功,可以访问其他请求");
}
}
}
Cookie和Session的区别与联系
- 联系:Cookie和Session都是为了让服务端获取客户端提供的信息;提供的信息都是键值对形式的;Session技术是在Cookie技术的基础上进行的,都需要对请求头做处理
- 区别:
- 信息位置:
- Cookie是客户端技术,信息存储在客户端(浏览器),也意味着前端可以操作
- Session是服务器技术,信息储存在服务器
- 敏感性:
- Cookie共享的是常规信息,直接抓包获取的是对应的值
- Session通常共享的是敏感信息,直接抓包获取的是id
- 值类型:
- Cookie的值为String字符串
- Session的值为Object
- 跨应用:
- Cookie信息可以跨应用共享
- Session信息局限于当前应用
- 信息位置:
数据共享
- Request域:存在转发关系的Servlet之间的数据共享,比如Servlet和JSP数据共享
- Context域:整个应用之中的数据共享,比如整个应用中的SqlSessionFactory、properties等
- Session域:同一个用户(客户端)中的数据共享,比如登录之后的用户信息