会话
会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接·,会话结束。再一次会话中可以包含多次请求和响应。
(也可以理解为:一个IP地址所进行访问web服务器的资源,只要双方都不断开连接会话一直进行,但只要有一方断开连接就会自动断开此次会话)会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求之间共享数据。
会话跟踪的方案:
1、客户端会话跟踪技术:Cookie
2、服务端会话跟踪技术:Session
3、令牌技术
注意
为什么要使用会话跟踪?
因为先在的网站请求/响应多数都是遵守HTTP协议的,所以这样子会使得在请求服务器或者响应是无状态的,会给我们带来了许多的便捷,但是正是因为无状态,不可以在同一次会话的多次请求之间共享数据。
什么是共享数据?
就是在同一次会话的请求之间所携带的数据进行共享。
一、客户端会话跟踪技术:Cookie
概念
优点:
HTTP协议中支持的技术
此优点所带来的好处;
三个自动:
服务器会自动的将Cookie响应给浏览器,
浏览器会自动的将Cookie存储在浏览器本地,
在后续的请求中浏览器会自动的携带Cookie进行请求。
他会带来一个响应头(Set-Cookie)和一个请求头(Cookie)
响应头(Set-Cookie)
HTTP响应头被用于从服务器向用户代理发送cookie。
请求头(Cookie)
HTTP请求头包含存储嫌弃那通过与所述服务器发送的Set-Cookie头。
缺点:
1、移动端APP无法使用Cookie
2、不安全,用户可以自己禁用Cookie
3、Cookie不能跨域
什么是跨域?
在浏览器进行访问时会向前端地址进行访问,然而在需要向服务器进行传递时若发现服务器与前端地址的IP/域名、端口不一样,此时在进行访问的话就相当于是跨域访问了。
跨域可以直接理解为:三个维度(协议、IP/域名、端口)
代码演示
这里的是直接在SpringBoot的控制层进行写入代码进行实现
// 设置Cookie
@GetMapping("/c1")
public Result cookiel(HttpServletResponse response){
response.addCookie(new Cookie("login_username","itheima"));//设置Cookie/响应Cookie
return Result.success();
}
// 获取Cookie
@GetMapping("/c2")
public Result cookiel2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();//获取所有的Cookie
for (Cookie cookie : cookies){
if (cookie.getName().equals("login_username")){//获取Cookie的name为:login_username的值
log.info("login_username: {}",cookie.getValue());
}
}在这里插入图片描述
return Result.success();
}
**执行“/c1”**可以在响应头中看到客户端所响应的Set Cookie
**执行“/c2”**可以在请求头中看到客户端所响应的Cookie
有上述代码演示以及上述的图片,可以看到了数据的共享
二、服务度爱你会话跟踪技术:Session
概念:
Session是基于Cookie进行优化的一个会话跟踪方案。
优化原理:将Cookie的name固定设置为:jsessionid,而value的值将会随机ID(一组随机的字符串),凭借这个ID就可以向服务端进行此次会话数据的共享
优点:
cookie的value存储在服务端,且更加的安全
因为此时的Cookie的value值是Session会话随机生成的ID,并不会直接将数据暴露在客户端,所以相对于Cookie会话更加的安全。
缺点:
1、移动端APP无法使用Cookie
2、Cookie不能跨域
3、服务器集群环境下无法直接使用Session
问题一:
什么是服务器集群环境?
简单地说,集群 就是指一组(若干个)相互独立的计算机,利用高速通信网络组成的一个较大的 计算机服务系统,每个集群节点(即集群中的每台计算机)都是运行各自服务的独立服务器。这些服务器之间可以彼此通信,协同向用户提供应用程序,系统资源和数据,并以单一系统的模式加以管理。当用户请求集群系统时,集群给用户的感觉就是一个单一独立的服务器,而实际上用户请求的是一组集群服务器。
举个例子:
打开谷歌,百度的页面,看起来好简单,也许你觉得用几分钟就可以制作出相似的网页,而实际上,这个页面的背后是由成千上万台服务器集群协同工作的结果。问题二:
为什么服务器集群环境下无法直接使用Session?
因为使用Session会话时,服务器会自动生成一个Session ID,然而当服务器1已经对此次会话生成了一个Session ID,当客户端携带此ID向服务器2进行访问时,会发现服务器2,并没有此ID,所以无法使用Session会话。
代码演示
// 向HttpSession中存储值
@GetMapping("/s1")
public Result sessionl(HttpSession session){
log.info("HttpSession-s1: {}",session.hashCode());
session.setAttribute("loginUser","tom");//向session中存储数据
return Result.success();
}
// 从HttpSession中获取值
@GetMapping("/s2")
public Result session2(HttpServletRequest request){
HttpSession session = request.getSession();
log.info("HttpSession-s2: {}",session.hashCode());
Object loginUser = session.getAttribute("loginUser");//从session中获取数据
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}
**执行“/s1”**可以在响应头中看到客户端中生成的Session ID
**执行“/s1”**可以在请求头中看到客户端中生成的Session ID
有上述代码演示以及上述的图片,可以看到了数据的共享
三、JWT令牌技术
概念:
简介:JWT(全称:JSON Web Token)
定义了一种简洁的、自包含的格式,用于在通讯双方以json数据格式安全的传输信息。
(由于数字签名的存在,这些信息是可靠且安全的)
自包含:一个组件能够独立地进行编译,不依赖于其他组件的特定包含顺序
JWT令牌的组成:
- 第一部分:Header(头),记录令牌的类型,签名的算法等。列如:
{"atg":"HS256","type":"jwt"}
- 第二部分:Payload(有效负载),携带一些自定义的信息,默认信息等。列如:
{"id":"2","username":"Tom","exp": 1730722706}
- 第三部分:Signature(签名),防止Token被篡改,确保安全性。将header、payload,并加入秘钥,通过指定的签名的算法而来
注意:
1、JWT令牌每一个部分都是以".“来进行分割
2、JWT令牌在请求头中的存储数据为:
Header 头:eyJhbGciOiJIUzI1NiJ9
.
Payload(有效负载):eyJuYW1lIjpudWxsLCJJZCI6MCwiZXhwIjoxNzMwNzIyNzA2LCJ1c2VybmFtZSI6ImNoeWIyIn0
.
Signature(签名):tLWilH7eo5tn3k21bb5_o-1KYrV6wOVNhicMLoWDi2E
到这里可能会有疑问为什么会是这种毫无规律的一串字符串?
其实是因为JWT令牌会将Header头与Payload两部分使用Base64编码的形式进行编码,之后在以”."符号进行连接。
至于Signature(签名) 是使用了 JWT中的某个算法再加上**秘钥(此秘钥是自己设置的一长串字符串)**这样着更加的保证了数据的安全。
优缺点
优点:
支持PC端、移动端
解决了集群环境下的认证问题
减轻服务器端存储的压力
缺点:
需要自己实现
JWT的使用(场景:登录认证)
一、登陆成功后,生成令牌
概念:当登录成功之后,为了方便我们一般会先给前端发送一个JWT令牌,之后的对话中只携带此令牌就不需要在去登录。
代码实现
1、导入依赖
注意:在实现代码之前我们需要先去导入两个依赖:jjwt、jaxb-api
<!-- jwt令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- jaxb-api-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
2、调用方法,生成令牌
String s = "360ede5c9fc444a2ac43f6821008d4c1";//秘钥
String jwt = Jwts.builder()//调用此方法来进生成Jwt令牌
.signWith(SignatureAlgorithm.HS256,s)//设置JWT中的某个算法,以及添加秘钥
.setClaims(claims)//设置自定义的数据列如:{“Id”:1,"name":"残花月伴"}
.setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000))//设置JWT令牌有效时间,(当前时间+num毫秒)
.compact();
System.out.println("生成的Jwt为:"+jwt);
3、将生成好的令牌,传给前端
这里就直接一Json格式的形式进行返回前端即可,剩下的就交给前端了,
注意:当传给前端之后,前端会将此次会话的每一次请求都会携带此令牌,一般会将他进行封装到一个名叫“Token”的请求头中,
列如:
而这时就需要去校验令牌是否是正确。
二、校验令牌
概念:在校验令牌之前我们需要知道如何判断此令牌是否正确,这就要去解析Jwt
- 解析Jwt:
- 解析之前我们需要拿到两个变量:一个是Jwt,一个是此令牌的秘钥
- 代码演示:
Claims claims = Jwts.parser()//调用解析Jwt的方法
.setSigningKey("360ede5c9fc444a2ac43f6821008d4c1")//传递秘钥
.parseClaimsJws(jwt)//传递Jwt的值
.getBody();
System.out.println(claims);
如何校验令牌:
方法一: 使用 过滤器(Fiter) 将令牌无效的请求进行过滤掉
方法二: 使用拦截器将令牌无效的请求进行拦截
这里因为内容排版问题过滤器(Fiter)与拦截器(Interceptor)
大家可以看我下一章的内容(过滤器(Fiter)与拦截器(Interceptor))