嚎羸的博客

因为Hexo是静态博客,部署多有不便,建议查看我的语雀文档

0%

JWT

单点登录介绍

单一服务器模式

使用session对象实现登录

登陆成功之后,把用户数据放到session中

判断是否登录,可以从session中获取数据,获得登录状态

但是session模式有缺点,因为session在不同的服务器中不能共享,所以这就导致我们在不同的服务器上需要重复登录,这一点是非常不好的

我们想要的结果是在一个服务器上登录,其他服务器全部共享。

这个有一个术语,叫做单点登录

SSO模式

SSO,Single Sign On,中文名叫做单点登录

单点登录有很多种实现方式,这里来介绍一下三种比较常见的机制:

第一种:session广播

session广播其实就是session的复制过程,它的大体流程是:先在一个服务器上进行登录,然后在另一台服务器上复制这台服务器上的session,这样就达到了所有信息在所有服务器上共享,达到单点登录的效果

但是虽然可以做到,但是有一个致命的缺点:假如我们有几十个模块,难道要进行复制几十次么?这是对资源的极大消耗,所以这个方法在几台服务器的时候还可以用一下,其他情况不太好使

第二种:cookie+redis

cookie,是一种客户端技术,也就是存到浏览器中,每次发送请求都会带着cookie发送

redis,是一种内存数据库,而且是基于key-value的存储

所以它的存储过程是这样的:

1、用户登录

用户登录成功之后,将数据放到两个地方:cookie和redis

对于redis,我们的value存储的就是用户信息,我们的key存储的是一个随机生成的唯一id(一般来讲有些人喜欢根据id,ip等等生成)

对于cookie,我们存储这个redis的key值

2、访问项目中的其他模块,发送请求时带着cookie

cookie中含有redis的key,我们拿着cookie值去查询是否能查询到value,如果能查询到就成功登陆

第三种:token

token,也叫令牌,它是按照一定的规则生成的字符串,字符串里包含用户信息

1、登录时

比如我们可以把用户信息做个编码,然后加密一下,这就是token,至于怎么编码,怎么加密就是你自己的问题

最后的这个字符串返回,你可以放到cookie里面,或者地址栏里面,或者请求头里面等等,反正只要返回即可

2、在其他服务器上

我们每次带着token发送到后端,后端解码获取用户信息,假如可以获取到用户信息,那么就说明登录成功,假如获取不到就说明登录失败


而且我们还有一个内容,session本来有一个默认的过期时间:30分钟

我们的第二种和第三种也可以设置过期时间

第二种:redis和token也可以设置过期时间,达到和session一样的效果


JWT

JWT概述

我们刚才讲到,token是按照一定的规则封装信息。这里有一个术语叫做自包含令牌

我们说按照一定规则,这个规则每个人想到的都不一样,根据这个,有一个比较通用的规则:JWT,所以JWT把规则已经给我们规定好了

image-20201104113101257

图中的这一长串字符串就是我们的规则

  • 红色部分:固定的,JWT头信息
  • 紫色部分:有效载荷:包含主体信息(用户信息)
  • 蓝色部分:签名哈希,防伪标志

JWT使用

1、引入依赖

1
2
3
4
5
<!-- JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>

2、使用JWT的工具类,这个工具类要会改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
* @author
*/
public class JwtUtils {

//token的过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;
//字符的加密的密钥,随便写的
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

//生成token字符串的方法,这里只传递了两个值,一个id一个用户昵称,但其实可以传递多个值
public static String getJwtToken(String id, String nickname){

String JwtToken = Jwts.builder()
/*
设置JWT的头信息,这个是固定的
*/
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
/*
设置token字符串的过期时间,如果要改变就改变分类和token的过期时间
*/
//分类,随便起的
.setSubject("guli-user")
.setIssuedAt(new Date())
//当前时间+时间成为过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//设置token的主体部分
.claim("id", id)
.claim("nickname", nickname)
//签名哈希,根据密钥和加密方式生成签名哈希
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();

return JwtToken;
}

/**
* 判断token是否存在与有效,根据密钥来判断
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 判断token是否存在与有效,根据httpservletrequest中的header得到token,然后判断是否有效,功能和上面的一样
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 根据token获取token字符串中的数据
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}