Cookie/Session
回顾Cookie
HTTP 协议⾃⾝是属于 “⽆状态” 协议
“⽆状态” 的含义指的是:
默认情况下 HTTP 协议的客⼾端和服务器之间的这次通信, 和下次通信之间没有直接的联系.
HTTP是没有记忆功能的
现在请求和过一会儿请求,同样的请求参数,得到的结果是一样的(处理逻辑一样,而不是指数据)
但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的
例如登陆⽹站成功后, 第⼆次访问的时候服务器就能知道该请求是否是已经登陆过了.
Cookie是客户端机制,Session是服务器机制
上述图中的 “令牌” 通常就存储在 Cookie 字段中.
⽐如去医院挂号
看病之前先挂号. 挂号时候需要提供⾝份证号, 同时得到了⼀张 “就诊卡”, 这个就诊卡就相当于患者的 “令牌”.
后续去各个科室进⾏检查, 诊断, 开药等操作, 都不必再出⽰⾝份证了, 只要凭就诊卡即可识别出当前患者的⾝份.
看完病了之后, 不想要就诊卡了, 就可以注销这个卡. 此时患者的⾝份和就诊卡的关联就销毁了. (类似于⽹站的注销操作)
⼜来看病, 可以办⼀张新的就诊卡, 此时就得到了⼀个新的 “令牌”
此时在服务器这边就需要记录"令牌"信息, 以及令牌对应的⽤⼾信息, 这个就是 Session 机制所做的⼯作.
理解Session
我们先来了解⼀下什么是会话.
会话: 对话的意思
在计算机领域, 会话是⼀个客⼾与服务器之间的不中断的请求响应. 对客⼾的每个请求,服务器能够识别出请求来⾃于同⼀个客⼾. 当⼀个未知的客⼾向Web应⽤程序发送第⼀个请求时就开始了⼀个会话.当客⼾明确结束会话或服务器在⼀个时限内没有接受到客⼾的任何请求时,会话就结束了.
⽐如我们打客服电话
每次打客服电话, 是⼀个会话. 挂断电话, 会话就结束了
下次再打客服电话, ⼜是⼀个新的会话
如果我们⻓时间不说话, 没有新的请求, 会话也会结束
服务器同⼀时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是从属于哪个⽤⼾, 也就是属于哪个会话, 就需要在服务器这边记录每个会话以及与⽤⼾的信息的对应关系.
Session是服务器为了保存⽤⼾信息⽽创建的⼀个特殊的对象.
Session的本质就是⼀个 “哈希表”, 存储了⼀些键值对结构. Key 就是SessionID, Value 就是⽤⼾信息(⽤⼾信息可以根据需求灵活设计).
SessionId 是由服务器⽣成的⼀个 “唯⼀性字符串”, 从 Session 机制的⻆度来看, 这个唯⼀性字符串称为 “SessionId”. 但是站在整个登录流程中看待, 也可以把这个唯⼀性字符串称为 “token”.
上述例⼦中的令牌ID, 就可以看做是SessionId, 只不过令牌除了ID之外, 还会带⼀些其他信息, ⽐如时间, 签名等.
-
当⽤⼾登陆的时候, 服务器在 Session 中新增⼀个新记录, 并把 sessionId 返回给客⼾端. (通过 HTTP 响应中的 Set-Cookie 字段返回).
-
客⼾端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId. (通过 HTTP 请求中的 Cookie 字段带上).
-
服务器收到请求之后, 根据请求中的 sessionId 在 Session 信息中获取到对应的⽤⼾信息, 再进⾏后续操作.找不到则重新创建 Session, 并把SessionID 返回
Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.
这是Session存在的问题,解决方式是通过令牌的方式
Cookie 和 Session 的区别
两个均是会话机制
会话:对话
- Cookie 是客⼾端保存⽤⼾信息的⼀种机制. Session 是服务器端保存⽤⼾信息的⼀种机制.
Cookie是客户端机制,Session是服务器机制
-
Cookie 和 Session之间主要是通过 SessionId 关联起来的, SessionId 是 Cookie 和 Session 之间的桥梁
-
Cookie 和 Session 经常会在⼀起配合使⽤. 但不是必须配合.
-
完全可以⽤ Cookie 来保存⼀些数据在客⼾端. 这些数据不⼀定是⽤⼾⾝份信息, 也不⼀定是 SessionId
-
Session 中的 sessionId 也不需要⾮得通过 Cookie/Set-Cookie 传递, ⽐如通过URL传递
-
获取Cookie
Spring MVC是基于 Servlet API 构建的原始 Web 框架, 也是在Servlet的基础上实现的
HttpServletRequest
,HttpServletResponse
是Servlet提供的两个类, 是Spring MVC⽅法的内置对象. 需要时直接在⽅法中添加声明即可.
HttpServletRequest
对象代表客⼾端的请求, 当客⼾端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的⽅法,可以获得客⼾端请求的所有信息.
HttpServletResponse
对象代表服务器的响应. HTTP响应的信息都在这个对象中, ⽐如向客⼾端发送的数据, 响应头, 状态码等. 通过这个对象提供的⽅法, 可以获得服务器响应的所有内容Spring MVC在这两个对象的基础上进⾏了封装, 给我们提供更加简单的使⽤⽅法.
@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request, HttpServletResponse response){
//这两个参数是Spring的内置对象,需要使用时,直接在方法声明即可
Cookie[] cookies = request.getCookies();
//for (Cookie cookie : cookies) {
// System.out.println(cookie.getName()+":"+cookie.getValue());
//}
Arrays.stream(cookies).forEach(cookie->{
System.out.println(cookie.getName()+":"+cookie.getValue());
});
return "获取cookie成功";
}
此时在浏览器输入http://127.0.0.1:8080/param/getCookie
后,在idea控制台就会显示
java.lang.NullPointerException: null
at java.util.Arrays.stream(Arrays.java:5004) ~[na:1.8.0_271]
at com.example.demo.controller.ParamController.getCookie(ParamController.java:85) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_271]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_271]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_271]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_271]
修正后
@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request, HttpServletResponse response){
//这两个参数是Spring的内置对象,需要使用时,直接在方法声明即可
Cookie[] cookies = request.getCookies();
//for (Cookie cookie : cookies) {
// System.out.println(cookie.getName()+":"+cookie.getValue());
//}
if(cookies!=null) {
Arrays.stream(cookies).forEach(cookie -> {
System.out.println(cookie.getName() + ":" + cookie.getValue());
});
}
return "获取cookie成功";
}
以上是传统获取cookie的方式
简洁获取cookie的方式
@CookieValue
@RequestMapping("/getCookie2")
public String getCookie(@CookieValue String bite){
return "cookie中存储的值bite:"+bite;
}
因为Cookie是客户端机制,与浏览器有关,因此重启服务器后Cookie仍然存在,但以上通过注解只能拿到一个cookie
获取session
Session 存储和获取
Session是服务器端的机制, 我们需要先存储, 才能再获取
Session 也是基于 HttpServletRequest
来存储和获取的
Session存储
因为Session是服务器的,无法伪造
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request){
HttpSession session=request.getSession();
session.setAttribute("username","zhangsan");
return "success";
}
这个代码中看不到 SessionId 这样的概念的. getSession 操作内部提取到请求中的 Cookie ⾥的 SessionId, 然后根据 SessionId 获取到对应的 Session 对象, Session 对象⽤ HttpSession来描述
HttpSession getSession(boolean create) : 参数如果为 true, 则当不存在会话时新建会话; 参数如果为 false, 则当不存在会话时返回 null
HttpSession getSession(): 和getSession(true) 含义⼀样, 默认值为true.
void setAttribute(String name, Object value): 使⽤指定的名称绑定⼀个对象到该 session 会话
Session读取
读取 Session 可以使⽤ HttpServletRequest
@RequestMapping("/getSession")
public String getSession(HttpServletRequest request){
HttpSession session=request.getSession(false);//true的话不存在session就创建一个,false不存在就不创建
//默认值为true
//根据sessionId获取session对象,类似根据学号找到整个学生
if(session!=null){
String username= (String) session.getAttribute("username");
return "登录用户:"+username;
}
return "session 为空";
}
Object getAttribute(String name): 返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null
setSession
通过fiddler观察
setSession
通过浏览器观察
getSession
通过fiddler观察
可以看到, Http请求时, 把SessionId通过Cookie传递到了服务器.
getSession
通过浏览器观察
简洁获取 Session (1)
@SessionAttribute
@RequestMapping("/getSession2")
public String getSession2(@SessionAttribute String username){
return "username:"+username;
}
重启服务器后要先启动setSession
因为这是服务器机制的,重启服务器后就清空了,默认是存在内存中的,重启后就清空了
使用这种方式的话,Session不存在,浏览器就会报错,因为
@SessionAttribute
底层要求这个session是必传的,不传就报错了@RequestMapping("/getSession2") public String getSession2(@SessionAttribute(required = false) String username){ return "username:"+username; }
或者改成这样就不会报错,只会在浏览器中显示
null
在使用
setSession
后同样可以通过getSession2
获取到session
简洁获取 Session (2)
通过Spring MVC内置对象 HttpSession 来获取
@RequestMapping("/getSession3")
public String getSession3(HttpSession session){
String username= (String) session.getAttribute("username");
return "登录用户:"+username;
}
HttpSession session = request.getSession();
Session 不存在的话, 会⾃动进⾏创建
HttpSession session
这个参数等同于HttpSession session=request.getSession(true)
在使用
setSession
后同样可以通过getSession3
获取到session
获取Header
传统获取 header
获取Header也是从 HttpServletRequest
中获取
@RequestMapping("/getHeader")
public String getHeader(HttpServletRequest request){
String userAgent = request.getHeader("User-Agent");
return "userAgent:"+userAgent;
}
使⽤
HttpServletRequest
提供的 getHeader ⽅法来获取, 参数对应HTTP请求报头的"Key"
通过fiddler观察
通过浏览器观察
简洁获取 Header
@RequestHeader
@RequestMapping("/getHeader2")
public String getHeader2(@RequestHeader("User-Agent")String userAgent){
return "userAgent:"+userAgent;
}
@RequestHeader注解的参数值为HTTP请求报头中的"Key"
即
@RequestHeader("User-Agent")
这部分的内容要求和上面fiddler观察力的User-Agent保持一样才对,后面参数改名为别的都可以