参考教程: JWT认证原理、流程整合springboot实战应用,前后端分离认证的解决方案!

创建JWTUtils工具类

  1. @Component
  2. public class JWTUtils {
  3. private static final String salt = "!hfuw3y*&";
  4. /**
  5. * 生成token
  6. * @param map 传入的参数 如用户名,用户id等信息
  7. * @return
  8. */
  9. public static String getToken(Map<String,String> map){
  10. Calendar instance = Calendar.getInstance();
  11. instance.add(Calendar.DATE,7);//默认7天过期
  12. //创建jwt builder
  13. JWTCreator.Builder builder = JWT.create();
  14. //payload
  15. map.forEach((k,v)->{
  16. builder.withClaim(k,v);
  17. });
  18. //签名并设置过期时间
  19. String token = builder.withExpiresAt(instance.getTime())
  20. .sign(Algorithm.HMAC256(salt));
  21. return token;
  22. }
  23. /**
  24. * 验证合法性并获取token信息,如果非法则直接抛异常
  25. * @param token
  26. * @return
  27. */
  28. public static DecodedJWT verifyToken(String token){
  29. DecodedJWT verify = JWT.require(Algorithm.HMAC256(salt)).build().verify(token);
  30. return verify;
  31. }
  32. /**
  33. * 从jwt中获取userId
  34. */
  35. public static String getUserIdFromToken(String token){
  36. DecodedJWT verify = JWT.require(Algorithm.HMAC256(salt)).build().verify(token);
  37. return verify.getClaim("userId").asString();
  38. }
  39. }

JWT使用

用户首次访问,请求数据库,进行验证

  • 该逻辑比较简单,下面代码仅供参考
  1. /**
  2. *登录
  3. * @param request
  4. * @return
  5. */
  6. @Override
  7. public UserLoginResponse login(UserLoginRequest request) {
  8. //提前设置错误的返回信息
  9. UserLoginResponse response = new UserLoginResponse();
  10. response.setUserId(Convert.toLong(0));//0表示登录失败
  11. //状态码是用户名或密码不正确
  12. response.setCode(StatusCode.USERORPASSWORD_ERRROR.getCode());
  13. response.setMsg(StatusCode.USERORPASSWORD_ERRROR.getMessage());
  14. try {
  15. //查询名字相同的用户
  16. QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();
  17. queryWrapper.eq("user_name",request.getUsername());
  18. UserEntity user = userDao.selectOne(queryWrapper);
  19. //如果存在名字相同且密码相同的用户则返回
  20. if(user!=null && user.getUuid()>0){
  21. String md5Password = MD5Util.encrypt(request.getPassword());
  22. //密码相同,设置响应信息
  23. if (user.getUserPwd().equals(md5Password)) {
  24. response.setUserId(user.getUuid());
  25. response.setCode(StatusCode.SUCCESS.getCode());
  26. response.setMsg(StatusCode.SUCCESS.getMessage());
  27. }
  28. }
  29. } catch (Exception e) {
  30. log.error("login:",e);
  31. return response;
  32. }
  33. return response;
  34. }

验证通过,服务端使用JWT生成token

  • 调用上述登录功能,得到返回结果
  • 若结果中的userId等于0,表示登录失败,返回账号或密码错误
  • 否则登录成功。将userId填入payload对象中,调用JWTUtils的getToken方法生成令牌,并把令牌写入response中返回。
  1. @RequestMapping("/auth")
  2. public ResponseData generateToken(AuthRequest authRequest){
  3. log.info("用户名:[{}]",authRequest.getUserName());
  4. log.info("密码:[{}]",authRequest.getPassword());
  5. //获取用户传入的用户名和密码,封装成request对象
  6. UserLoginRequest request = new UserLoginRequest();
  7. request.setUsername(authRequest.getUserName());
  8. request.setPassword(authRequest.getPassword());
  9. UserLoginResponse response = userService.login(request); //进行登录
  10. //0表示登录失败
  11. if(response.getUserId()!=0) {
  12. Map<String, String> payload = new HashMap<>();
  13. payload.put("userId", response.getUserId().toString());
  14. //生成JWT的令牌
  15. String token = JWTUtils.getToken(payload);
  16. response.setToken(token);
  17. return new ResponseUtil<UserLoginResponse>().setData(response);
  18. }else{
  19. return new ResponseUtil<>().setErrorMsg("账号或密码错误");
  20. }
  21. }

用户下次请求携带token,需要验签

1. 编写拦截器类,实现preHandle 方法
  • 该方法在请求处理之前调用,适合用来验签
  • 首先获取请求头中携带的token,然后调用JWTUtils的verifyToken方法进行验证。
  • 若方法报错,则通过try-catch模块进行捕获,异常包括签名异常、token过期异常或算法不匹配异常等。
  • 将异常状态记录到map中,转为json写入到响应报文中。
  • 代码实现:```java @Slf4j @Component public class JWTInteceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    1. log.info("被拦截到了");
    2. //1.获取请求头中的token
    3. String token = request.getHeader("token");
    4. //2.验证token
    5. Map<String,Object> map = new HashMap<>();
    6. try {
    7. JWTUtils.verifyToken(token);//验证令牌
    8. return true;
    9. } catch (SignatureVerificationException e) { //签名异常
    10. map.put("msg", "无效签名");
    11. }catch (TokenExpiredException e){ //token过期异常
    12. map.put("msg", "token过期");
    13. }catch (AlgorithmMismatchException e){ //算法不匹配异常
    14. map.put("msg", "算法不匹配");
    15. }catch (Exception e){
    16. map.put("msg", "无效签名");
    17. }
    18. map.put("state",false); //设置状态
    19. //将map转为json
    20. String json = new ObjectMapper().writeValueAsString(map);
    21. response.setContentType("application/json;charset=UTF-8");
    22. response.getWriter().println(json);//写到响应报文中
    23. return false;
    } } ```

2. 编写拦截器配置类
  1. @Configuration
  2. public class InterceptorConfig implements WebMvcConfigurer {
  3. @Override
  4. public void addInterceptors(InterceptorRegistry registry) {
  5. //原则上拦截所有请求,但需要配置忽略列表
  6. registry.addInterceptor(new JWTInteceptor())
  7. .addPathPatterns("/**")
  8. .excludePathPatterns("/auth")
  9. .excludePathPatterns("/user/register")
  10. .excludePathPatterns("/user/checkName")
  11. .excludePathPatterns("/bus);
  12. }
  13. }

3. 验签通过,放行请求

测试JWT的SSO单点登录

未携带token,直接访问不在忽略列表中的url

20201012162637269.png
20201012162653409 (1).png

用户首次登录返回token

20201012162743674.png

20201012162803214.png
20201012162824458.png