0%

【JWT】JWT学习

关于如何使用JWT来进行用户登录验证,实现网络安全

1. JWT介绍

  • JWT:JSON Web Token(JSON网路令牌)
  • JWT官网:https://jwt.io
  • JWT通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、前面等相关处理

2. JWT作用

  • (1)授权:
    • JWT的最常见方案,一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌徐云的路由,服务和资源。
  • (2)信息交换
    • JWT是各方之间安全传输信息的好方法。因为可以对JWT进行签名,可以确保发件人没有变化。

3.选择JWT理由

3.1 基于传统的Session认证

  • 客户向服务器登录后,服务器会生成一个Session对象,将用户ID保存在里面,并在第一次响应客户端时,发送一个包含SessionID的Cookie;客户端每次访问服务器时都会带上次Cookie,来判断是否已经登录验证。
  • 暴露问题:
    • (1)Session保存在内存中,随着用户量增大,服务器开销增大
    • (2)Session保存在内存中,导致在分布式系统中,用户永远只能访问此服务器,限制了负载均衡能力
    • (3)基于Cookie进行用户验证,如果Cookie被拦截,容易受到攻击
    • (4)前后端分离系统中,可以需要多个代理才发送带后端,每次转发都携带Cookie,十分麻烦

3.2 基于JWT认证

  • JWT优势:
    • (1)简洁:可以通过URL, POST参数或者在http header发送,因为数据量小,传输速度也很快
    • (2)自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库
    • (3)跨语言:基于Json加密形式保存在客户端,原则上任何web形式都支持
    • (4)不需要再服务器的session中保存,特别适合永野芽郁分布式系统

4. JWT结构

4.1 令牌组成

  • 由标头(header)、有效载荷(payload)、签名(signature)组成
  • 组成形式为:header.payload.signature,中间有’.’隔开

4.2 Header

  • header由两部分组成:令牌类型和所使用的签名算法(HMAC、SHA256、RSA)。后端会使用Base64编码组成header
1
2
3
4
{
"alg": "HS356", //算法
"typ": "JWT" //类型
}

4.3 Payload

  • payload,其中包含声明。声明式有关实体和其他数据,按照实际情况返回。同样会使用base64编码组成
1
2
3
4
5
{
"sub": 123456789,
"name": "letere",
"admin": true
}

4.4 Signature

  • signature需要编码后的header和payload以及一个密钥,然后使用header中的算法进行签名,签名的作用是保证JWT没有被篡改过。

  • 例:算法(编码后的header + “.” + 编码后的payload, 密钥)


5. JWT基本使用

5.1 依赖导入

1
2
3
4
5
6
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.12.0</version>
</dependency>

5.2 生成令牌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test() {
Calendar date = Calendar.getInstance();
date.add(Calendar.SECOND, 120); //设置时间120秒

Map<String, Object> header = new HashMap<String, Object>();
String token = JWT.create()
.withHeader(header) //header,传入一个map<String, Object>,可省略,默认值一样
.withClaim("userId", 12) //payload,可以设置多个
.withClaim("userName", "letere")
.withClaim("sex", "男")
.withExpiresAt(date.getTime()) //设置token过期时间
.sign(Algorithm.HMAC256("XJBX")); //signature,算法类选一个算法,参数为密钥

System.out.println(token);
}

5.3 验证令牌

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test2() {
//JWT验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("XJBX")).build();

//验证令牌(注意令牌是否过期)
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXgiOiLnlLciLCJ1c2VyTmFtZSI6ImxldGVyZSIsInVzZXJJZCI6MTJ9.DEwv6wVClwxjVO_wsMBojPSOkckJh3PEaAv543bSy-w"); //参数为生成的token

//获取令牌中数据
System.out.println(verify.getClaim("userId").asInt());
System.out.println(verify.getClaim("userName").asString());
System.out.println(verify.getClaim("sex").asString());
}

6. 与SpringBoot的结合使用

6.1 依赖引入

1
2
3
4
5
6
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.12.0</version>
</dependency>

6.2 接口创建

  • 由于只是个简单的demo,所以身份验证连接数据库就行验证,只是个简单的判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Service层
public Map<String, Object> login(String userName, String password) {
Map<String, Object> message = new HashMap<>();
if("admin".equals(userName) && "123456".equals(password)) {
//生成token
Calendar date = Calendar.getInstance();
date.add(Calendar.DATE, 7);
String token = JWT.create()
.withClaim("userName", userName)
.withExpiresAt(date.getTime())
.sign(Algorithm.HMAC256("XJBX"));

message.put("state", true);
message.put("msg", "认证成功");
message.put("token", token);
}else {
message.put("state", false);
message.put("msg", "认证失败");
}
return message;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//Controller层
@RestController
public class UserController {
@Autowired
private UserServiceImp usi;

@GetMapping("/login")
public Map<String, Object> login(String userName, String password) {
return usi.login(userName, password));
}
//测试地址:http://localhost:8080/login?userName=admin&password=123456、
//返回JSON:{"msg":"认证成功","state":true,"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImFkbWluIiwiZXhwIjoxNjEwNTIzNTc3fQ.JJIrf0TdBwZgmrNxOu9cf-cbXrgN_ZiOuq_2UVEByMs"}
}

6.3 token拦截

  • 返回前端的token,在下次发送请求时,一般都将token放在请求头中,而后端用拦截器拦截请求,来验证token
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
//创建JWT拦截器
//拦截器
public class JwtIntercepter implements HandlerInterceptor {

//alt + shift + p
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头中的token
String token = request.getHeader("token");

//jwt验证器
try {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("XJBX")).build();
return true; //无异常,放行
} catch (Exception e) {
e.printStackTrace();
}
Map<String, String> message = new HashMap<>();
message.put("wrong", "出现异常");
//转成JSON发送给前端
String json = new ObjectMapper().writeValueAsString(message);
response.setContentType(json);
response.getWriter().println(json);
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
//SpringBoot配置自定义拦截器
@Configuration
public class IntercepterConfig implements WebMvcConfigurer {

//alt + shift + p
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtIntercepter())
.addPathPatterns("/user/**"); //添加要拦截的路径,链式编程
}
}