官网:https://jwt.io/

第一步,引入依赖

  1. <!--JWT生成token工具-->
  2. <dependency>
  3. <groupId>com.auth0</groupId>
  4. <artifactId>java-jwt</artifactId>
  5. <version>3.19.1</version>
  6. </dependency>

第二步、生成token的方法和验证的方法

  1. package com.tj.reggie.controller.utils;
  2. import com.auth0.jwt.JWT;
  3. import com.auth0.jwt.JWTCreator;
  4. import com.auth0.jwt.JWTVerifier;
  5. import com.auth0.jwt.algorithms.Algorithm;
  6. import com.auth0.jwt.exceptions.JWTVerificationException;
  7. import com.auth0.jwt.interfaces.DecodedJWT;
  8. import java.util.Calendar;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. public class JwtUtils {
  12. //签名,这里改成pubilc可以后面动态的改变SIGN密钥值
  13. pubilc static final String SIGN = "sign-content";
  14. /**
  15. * 生成token,header.payload.sign
  16. *
  17. * @return
  18. */
  19. public static String gettoken(Map<String, Object> map) {
  20. Calendar instance = Calendar.getInstance();
  21. instance.add(Calendar.DATE, 2); //默认2天过去
  22. //创建JWT builder
  23. JWTCreator.Builder builder = JWT.create();
  24. //payload
  25. map.forEach((k, v) -> {
  26. builder.withClaim(k, String.valueOf(v));
  27. });
  28. String token = builder
  29. //header
  30. .withHeader(map)
  31. //令牌的过期时间
  32. .withExpiresAt(instance.getTime())
  33. //sign签名
  34. .sign(Algorithm.HMAC256(SIGN));
  35. return token;
  36. }
  37. /**
  38. * 验证token
  39. *
  40. * @param token 返回DecodedJWT对象,包含token信息
  41. * @return
  42. */
  43. public static DecodedJWT checktoken(String token) {
  44. //创建验证对象
  45. return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
  46. }
  47. }

第三步,改写用户登录验证方法

  1. /**
  2. * 用户登录
  3. *
  4. * @param request
  5. * @param users
  6. * @return
  7. * @throws Exception
  8. */
  9. @Override
  10. public R login(HttpServletRequest request, Users users) throws Exception {
  11. //1.将页面提交的密码password进行sha256加密
  12. String password = Sha256Util.sha256(users.getPassword());
  13. //2.根据页面提交的用户名userName查询数据库
  14. LambdaQueryWrapper<Users> lqw = new LambdaQueryWrapper<>(); //条件对象
  15. lqw.eq(Users::getUser_accout, users.getUser_accout()); //判断数据库里是否有相同的用户名
  16. Users res = getOne(lqw); //查询一条记录
  17. //3.如果没有查询到则返回登录失败结果
  18. if (res == null) {
  19. return R.error("用户名不存在");
  20. }
  21. //4.密码比对,如果不一致则返回登录失败结构
  22. if (!res.getPassword().equals(password)) {
  23. return R.error("密码不正确");
  24. }
  25. //5.查看员工状态,如果为已禁用,则返回用户禁用信息
  26. if (res.getIsuse() == 1) {
  27. return R.error("账号已经被禁用");
  28. }
  29. //更新登录时间
  30. res.setTimein(LocalDateTime.now());
  31. //更新登录的IP地址
  32. res.setIpin(TjIpUtils.getIP(request));
  33. updateById(res);
  34. //6.登录成功,生成JWT的令牌
  35. HashMap<String, Object> payload = new HashMap<>();
  36. payload.put("id", res.getId());
  37. log.info("token写入的id:{}", res.getId());
  38. String userAccout = null;
  39. //name有中文,使用url编码,不然后期解码就是乱码
  40. userAccout = URLEncoder.encode(res.getUser_accout(), "UTF-8");
  41. payload.put("userAccount", userAccout);
  42. //JWT生成token令牌
  43. String token = JwtUtils.gettoken(payload);
  44. R success = R.success("登录成功");
  45. success.setData(token);
  46. //追加其他的数据
  47. appendOtherData(success, res);
  48. return success;
  49. }

第四步,改写doFilter拦截器

  1. package com.tj.demo.filter;
  2. import com.alibaba.fastjson.JSON;
  3. import com.auth0.jwt.exceptions.AlgorithmMismatchException;
  4. import com.auth0.jwt.exceptions.SignatureVerificationException;
  5. import com.auth0.jwt.exceptions.TokenExpiredException;
  6. import com.auth0.jwt.interfaces.DecodedJWT;
  7. import com.tj.demo.utils.BaseContext;
  8. import com.tj.demo.utils.JwtUtils;
  9. import com.tj.demo.utils.R;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.data.redis.core.RedisTemplate;
  13. import org.springframework.util.AntPathMatcher;
  14. import javax.servlet.*;
  15. import javax.servlet.annotation.WebFilter;
  16. import javax.servlet.http.HttpServletRequest;
  17. import javax.servlet.http.HttpServletResponse;
  18. import java.io.IOException;
  19. /**
  20. * 检查用户是否已经完成登录
  21. * "/*"是拦截所有访问
  22. */
  23. @WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
  24. @Slf4j
  25. public class LoginCheckFilter implements Filter {
  26. //路径匹配器,支持通配符
  27. public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
  28. @Autowired
  29. private RedisTemplate redisTemplate;
  30. @Override
  31. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  32. HttpServletRequest request = (HttpServletRequest) servletRequest; //转成http请求
  33. HttpServletResponse response = (HttpServletResponse) servletResponse; //转成http响应
  34. //1.获取本次请求的URI
  35. String requestURI = request.getRequestURI();
  36. log.info("拦截到请求:{}", requestURI);
  37. //2.定义不需要处理的请求路径
  38. String[] uris = new String[]{
  39. "/users/login",
  40. "/users/logout",
  41. "/users/avatar/**",
  42. "/login.html",
  43. "/logout.html",
  44. "/img/**",
  45. "/favicon.ico",
  46. "/common/**",
  47. "/static/**",
  48. };
  49. //3.判断本次请求是否需要处理
  50. boolean check = check(uris, requestURI);
  51. if (check) {
  52. //不需要处理的情况
  53. filterChain.doFilter(request, response);
  54. log.info("本次请求不需要处理:{}", requestURI);
  55. return;
  56. }
  57. //4.获取token,判断登录状态,如果已登录,则直接放行
  58. String token = request.getHeader("Authorization");
  59. String msg = null;
  60. response.setContentType("application/json;charset=UTF-8");
  61. //5、验证Redis里是否缓存了此token黑名单(token不能为空否则会报错)
  62. if (token != null && redisTemplate.opsForValue().get(token) != null) {
  63. msg = "token已经注销,请重新登录!";
  64. log.info(msg);
  65. //6.如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
  66. response.getWriter().write(JSON.toJSONString(R.logout(msg)));
  67. log.info("未登录,返回响应数据:{}", requestURI);
  68. return;
  69. }
  70. try {
  71. DecodedJWT decodedJWT = JwtUtils.checktoken(token);
  72. filterChain.doFilter(request, response);
  73. //当前请求的用户id
  74. Long userId = Long.valueOf(decodedJWT.getClaim("id").asString());
  75. //当前线程上设置用户id,BaseContext是自己编写的类,注意一下
  76. BaseContext.setUserId(userId);
  77. log.info("token验证通过,threadLocal线程缓存用户id为:{}", userId);
  78. return; //通过检测可以继续
  79. } catch (SignatureVerificationException e) {
  80. //更改了SIGN签名字段
  81. e.printStackTrace();
  82. msg = "服务器强制下线,请重新登录!!";
  83. log.info(e.getMessage());
  84. } catch (TokenExpiredException e) {
  85. //token设置的过期时间到了
  86. e.printStackTrace();
  87. msg = "登录时间长超过了2天,为了帐号安全,请重新登录!";
  88. log.info(e.getMessage());
  89. } catch (AlgorithmMismatchException e) {
  90. //算法不一样,这个一般不会改的。
  91. e.printStackTrace();
  92. msg = "token算法不一致!";
  93. log.info(e.getMessage());
  94. } catch (NullPointerException e) {
  95. e.printStackTrace();
  96. msg = "没有token密钥,无法访问服务器";
  97. log.info(e.getMessage());
  98. } catch (Exception e) {
  99. e.printStackTrace();
  100. msg = "token签名无效,请重新登录";
  101. log.info(e.getMessage());
  102. }
  103. //6.如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
  104. response.getWriter().write(JSON.toJSONString(R.logout(msg)));
  105. log.info("未登录,返回响应数据:{}", requestURI);
  106. }
  107. /**
  108. * 路径匹配,检查本次请求是否需要放行
  109. *
  110. * @param uris
  111. * @param requestURI
  112. * @return
  113. */
  114. public boolean check(String[] uris, String requestURI) {
  115. for (String uri : uris) {
  116. boolean match = PATH_MATCHER.match(uri, requestURI);
  117. if (match) return true;
  118. }
  119. return false;
  120. }
  121. }