springboot|SpringBoot集成JWT实现token验证

有关session存储用户信息在spring系列springsession文章中有写,session缺点是占用服务器资源,配置多台服务器后又需要对session进行统一存储(redis),保证每台服务器都可以取到正确的session。
JWT不用缓存数据库redis来实现用户信息的共享,也可以达到一次登录,处处可见
JWT全称JSON Web Token,实现过程简单的说就是用户登录成功之后,将用户的信息进行加密,然后生成一个token返回给客户端,与传统的session交互没太大区别。省掉了redis,把用户信息存到token中,这样客户端、服务端都可以从token中获取用户的基本信息,既然客户端可以获取,肯定是不能存放敏感信息的,因为浏览器可以直接从token获取用户信息。
交互流程:
springboot|SpringBoot集成JWT实现token验证
文章图片

1.JWT具体内容 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjU0MjIzMzAwLCJpYXQiOjE2NTQyMjMyNzAsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ.pO0gpz6AuDFtBAEOlTV09-BJVIIxqSGP-k_fcDrVhdw
1.header(头部)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
使用base64加密,用于存放token类型和加密协议.
springboot|SpringBoot集成JWT实现token验证
文章图片

2.payload(载荷)
eyJpZCI6MSwiZXhwIjoxNjU0MjIzMzAwLCJpYXQiOjE2NTQyMjMyNzAsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ
存放有效信息
springboot|SpringBoot集成JWT实现token验证
文章图片

3.signature
pO0gpz6AuDFtBAEOlTV09-BJVIIxqSGP-k_fcDrVhdw
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
2.springboot整合 1.pom

com.auth0 java-jwt 3.7.0

2.用户实体类
@Data public class User { private Long id; /** * 用户名 */ private String username; /** * 密码 */ private String password; }

3.TokenUtil工具类
用于创建、获取、验证Token
@Slf4j @Component public class TokenUtil {//密钥 public static final String SECRET = "youareapig??shabixiangpojie?"; //过期时间:秒 public static final int EXPIRE = 30; /** * 生成Token */ public static String createToken(User user){ Calendar nowTime = Calendar.getInstance(); //过期时间 nowTime.add(Calendar.SECOND, EXPIRE); Date expireDate = nowTime.getTime(); String token = JWT.create() //这是在设置第二部分信息,不要设置密码之类的,因为这些信息可以通过浏览器获取 //用户id .withClaim("id", user.getId()) //用户名 .withClaim("username",user.getUsername()) //创建token的时间 .withIssuedAt(new Date())//签名时间 //设置token的过期时间 .withExpiresAt(expireDate)//过期时间 //设置第一部分 .sign(Algorithm.HMAC256(SECRET)); //签名 return token; }/** * 验证token */ public static DecodedJWT verify(String token) { //如果有任何验证异常,此处都会抛出异常 我们需要在拦截器调用这个方法,捕获异常,然后返回错误信息给前端 DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token); return decodedJWT; }/** * 获取token中的 payload 也就是第二部分的信息 */ public static DecodedJWT getTokenInfo(String token) { DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token); //使用 TokenUtils.getTokenInfo(token).getClaim("account").asString() return decodedJWT; } }

4.拦截器
@Component public class AuthenticationInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { System.out.println("进入拦截器"); //实际这个名字可以指定为别的,token太没有辨识度--- //这个header是在创建完token返回给前端时指定的头部的key,vakue就是token内容 String token=httpServletRequest.getHeader("token"); Map map = new HashMap<>(); try { //这里尽行token验证,捕获异常,正常的话也不需要处理,直接抛出异常,由统一异常处理类进行处理,然后返回给前端统一数据类型。 TokenUtil.verify(token); return true; } catch (SignatureVerificationException e) { e.printStackTrace(); map.put("msg", "签名不一致"); map.put("code",500); } catch (TokenExpiredException e) { e.printStackTrace(); map.put("msg", "令牌过期"); map.put("code",500); } catch (AlgorithmMismatchException e) { e.printStackTrace(); map.put("msg", "算法不匹配"); map.put("code",500); } catch (InvalidClaimException e) { e.printStackTrace(); map.put("msg", "失效的payload"); map.put("code",500); } catch (Exception e) { e.printStackTrace(); map.put("msg", "token无效"); map.put("code",500); } //根据自己所需选择所需的异常处理 map.put("state", false); //响应到前台: 将map转为json String json = new ObjectMapper().writeValueAsString(map); httpServletResponse.setContentType("application/json; charset=UTF-8"); httpServletResponse.getWriter().println(json); return false; } }

5.注册拦截器
@Configuration public class WebConfig implements WebMvcConfigurer {@Override public void addInterceptors(InterceptorRegistry registry) { List excludePathLists= new ArrayList<>(); //注册、登录允许访问,不进行拦截 excludePathLists.add("/user/login"); excludePathLists.add("/user/register"); excludePathLists.add("/user/info"); registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/**").excludePathPatterns(excludePathLists); } }

参考链接
6.创建controller进行测试
@RestController public class UserController { //模拟登录 @PostMapping("/user/login") public String LoginUser(HttpServletResponse servletResponse){ //这里正常前端会传进来用户信息,然后去数据库查询,能查到,并且账号密码匹配成功,则可以登录。 //这里只是模拟,随便创建了一个用户信息 User user= new User(); user.setUsername("zhangsan"); user.setPassword("123456"); user.setId(1L); //根据用户信息创建token,(登录成功的处理逻辑) String myToken = TokenUtil.createToken(user); //设置header servletResponse.setHeader("token",myToken); //获取token,获取过期时间进行返回 DecodedJWT tokenInfo = TokenUtil.getTokenInfo(myToken); Date expiresAt = tokenInfo.getExpiresAt(); return expiresAt.toString(); } //测试其他账号登录 @PostMapping("/zhangsan") public String testLogin(){ return "ok"; } }

7.postman测试
【springboot|SpringBoot集成JWT实现token验证】1.最一开始还没登录过,肯定访问不到。
springboot|SpringBoot集成JWT实现token验证
文章图片

2.模拟登录成功,返回了过期时间
springboot|SpringBoot集成JWT实现token验证
文章图片

携带的header
springboot|SpringBoot集成JWT实现token验证
文章图片

3.再测试其他用户登录
携带token请求头
springboot|SpringBoot集成JWT实现token验证
文章图片

测试过期
springboot|SpringBoot集成JWT实现token验证
文章图片

至此一个简单的token登录机制就实现了~

    推荐阅读