2.jwt
JWT详解
1. 介绍
JWT简称 JSON Web Token,也就是通过**JSON形式作为Web应用中的令牌**,用于各方之间安全地将信息作为JSON对象传输,在数据传输的过程中还可以完成数据加密、签名等相关处理。
2. 流程图

2.1 认证流程
首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
后端核对用户名和密码成功后,将用户的id等其他信息作为 JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.mmm.sss的字符串。 token = head.payload.singurater
后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
后端检查是否存在,如存在验证JWT的有效性
2.2 jwt优势
简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
不需要在服务端保存会话信息,特别适用于分布式微服务。
3. JWT
3.1 header(表头)
标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256(默认)或RSA。它会使用Base64编码组成JWT结构的第一部分。
注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子的,它并不是一种加密过程。
3.2 Payload(载荷)
将能用到的用户信息放在 Payload中。官方建议不要放特别敏感的信息,例如密码。
3.3 Signature(签名信息)
签证由三部分组成,header和payload分别经base64Url(一种在base64上做了一点改变的编码)编码后由’.’连接,服务器生成秘钥(secret),连接之后的字符串在经header中声明的加密方式和秘钥加密,再用’.'和加密前的字符串连接。服务器验证token时只会验证第三部分。
header (base64Url) . payload (base64Url) . secret(header (base64Url)+payload (base64Url))
4. Session方式
session方法存储会有很大的缺点。
@RestController
@RequestMapping("test")
public class SessionController {
@PostMapping("login")
public String login(String userName, String password, HttpServletRequest request){
// 将用户名和密码拼接后设置在session作用域中
request.getSession().setAttribute("user", userName + password);
return "Login Success!";
}
}
5. Jwt实现
5.1 依赖
<!-- jwt的依赖包 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
5.2 工具类
package com.liu.jwt.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/*
* jwt工具类
*/
public class JwtUtils {
// sign
private static final String SIGN = "token@lms";
// 生成token,header.payload.sign
public static String getToken(Map<String, String> map){
// 创建jwt builder对象
JWTCreator.Builder builder = JWT.create();
// 添加payload
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
// 设置过期时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 7);
// 设置过期时间和添加签名信息
String token = builder.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256(SIGN));
return token;
}
// 验证token的合法性
public static void verify(String token){
// 如果验证失败会直接抛出异常信息
JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
// 获取token中的信息(也可以和上面部分的代码直接合并)
public static DecodedJWT getTokenInfo(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
return verify;
}
}
5.3 拦截器
package com.liu.jwt.interceptor;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liu.jwt.util.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* jwt拦截器,执行token的验证工作(集的配置拦截器)
*/
public class JwtInterceptor implements HandlerInterceptor {
/**
* 在目标方法执行之前执行
* @param request
* @param response
* @param handler 目标方法
* @return true,代表放行, false,表示禁止放行
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HashMap<String, Object> map = new HashMap<>();
// 因为token存放在请求头中,
String token = request.getHeader("token");
// 验证token
try {
// 直接验证token是否正确,如果token不正确,会直接抛出异常信息被全局异常捕获
JwtUtils.verify(token);
// 验证通过,直接放行请求
return true;
} catch (SignatureVerificationException e){
map.put("msg", "无效签名");
} catch (TokenExpiredException e){
map.put("msg", "token已过期");
} catch (AlgorithmMismatchException e){
map.put("msg", "token算法不一致");
} catch (Exception e) {
map.put("msg", "token无效");
}
map.put("code", "500");
// 将map转为json数据信息
// 因为response底层也封装了json,也可以使用fastJson
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(json);
// 禁止放行请求
return false;
}
}
配置拦截器
package com.liu.jwt.config;
import com.liu.jwt.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
// 拦截所有的请求信息
.addPathPatterns("/**")
// 只放行/login登录请求,
.excludePathPatterns("/login");
}
}
5.4 Controller
package com.liu.jwt.controller;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.liu.jwt.entity.User;
import com.liu.jwt.service.UserService;
import com.liu.jwt.util.JwtUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
public class UserController {
@Resource
private UserService userService;
/**
* 用户登录
* @param user
* @return
*/
@PostMapping("login")
public Map<String, Object> login(User user){
HashMap<String, Object> map = new HashMap<>();
System.out.println("user.getUsername() = " + user.getUsername());
System.out.println("user.getPassword() = " + user.getPassword());
try {
User userLogin = userService.login(user);
// 用户存放负载信息,从而调用jwt生成相应的token信息
HashMap<String, String> payload = new HashMap<>();
payload.put("id", userLogin.getId());
payload.put("username", userLogin.getUsername());
// 生成token信息
String token = JwtUtils.getToken(payload);
map.put("code", "200");
map.put("msg","登录成功");
map.put("token", token);
} catch (Exception e) {
map.put("code", "500");
map.put("msg", e.getMessage());
}
return map;
}
// 方式1:未使用拦截器,代码冗余
// 登录之后执行相应的业务逻辑
// @PostMapping("test")
public Map<String, Object> verify(String token){
HashMap<String, Object> map = new HashMap<>();
System.out.println("token = " + token);
// 验证token
try {
JwtUtils.verify(token);
// 获取token的返回值信息,类型为DecodedJWT
DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);
map.put("code", "200");
map.put("msg","请求成功");
return map;
} catch (SignatureVerificationException e){
map.put("msg", "无效签名");
} catch (TokenExpiredException e){
map.put("msg", "token已过期");
} catch (AlgorithmMismatchException e){
map.put("msg", "token算法不一致");
} catch (Exception e) {
map.put("msg", "token无效");
}
map.put("code", "500");
return map;
}
// 方式2:配置了拦截器(只有登录之后才能进行使用该方法)
@PostMapping("test")
public Map<String, Object> test(HttpServletRequest request){
HashMap<String, Object> map = new HashMap<>();
// 从请求头中获取token信息
String token = request.getHeader("token");
DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);
// 处理自己的业务逻辑
map.put("code", "200");
map.put("msg","请求成功");
String username = tokenInfo.getClaim("username").asString();
String id = tokenInfo.getClaim("id").asString();
map.put("username", username);
map.put("id", id);
return map;
}
}