分布式应用下登录检验解决方案

优缺点

JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。说白了就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息。生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库,可以存储在客户端,不占用服务端的内存资源,在前后端分离项目中经常使用。 JWT token可存储在cookie、localstorage和sessionStorage中。

缺点:

1)token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等。

2)如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥。

组成部分

JWT由三部分组成:头部(Header)、载荷(Payload)、签名(Signature)。

头部(header):主要是描述签名算法

复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}

负载(payload):主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户

复制代码
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

签名(signature):主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token

具体实现

依赖引入

XML 复制代码
<!-- JWT相关 -->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId>
	<version>0.7.0</version>
</dependency>

JWT核心代码:生成Token|解密Token

java 复制代码
@Slf4j
public class JWTUtil {
    /**
     * 主题
     */
    private static final String SUBJECT = "test";
    /**
     * 加密密钥
     */
    private static final String SECRET = "test.com";

    /**
     * 令牌前缀
     */
    private static final String TOKEN_PREFIX = "test-study";
    /**
     * token过期时间,7天
     */
    private static final long EXPIRED = 1000 * 60 * 60 * 24 * 7;

    /**
     * 生成token
     *
     * @param loginUser
     * @return
     */
    public static String geneJsonWebToken(LoginUser loginUser) {
        if (loginUser == null) {
            throw new NullPointerException();
        }
        String token = Jwts.builder().setSubject(SUBJECT)
//                配置payload(负载)
                .claim("head_img", loginUser.getHeadImg())
                .claim("account_no", loginUser.getAccountNo())
                .claim("username", loginUser.getUsername())
                .claim("mail", loginUser.getMail())
                .claim("phone", loginUser.getPhone())
                .claim("auth", loginUser.getAuth())
                .setIssuedAt(new Date())
                .setExpiration(new Date(CommonUtil.getCurrentTimestamp() + EXPIRED))
                .signWith(SignatureAlgorithm.HS256, SECRET).compact();
        token = TOKEN_PREFIX + token;
        return token;
    } 

    /**
     * 解密JWT
     * @param token
     * @return
     */
    public static Claims claimsJWT(String token){
        try {
            Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();
            return claims;
        } catch (Exception e) {
          log.error("解密失败");
          return null;
        }
    }
}

登录成功返回token

java 复制代码
//生成token令牌
LoginUser userDTO = new LoginUser();
BeanUtils.copyProperties(userDO, userDTO);
String token = JWTUtil.geneJsonWebToken(userDTO);
return JsonData.buildSuccess(token);

登录拦截器

java 复制代码
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 这段代码定义了一个静态的 ThreadLocal 对象 threadLocal,它的泛型参数是 LoginUser 类型,表示要存储的线程本地变量类型是 LoginUser。
     *
     * 在代码的第二行中,使用 threadLocal.set(loginUser) 方法将当前线程的 threadLocal 变量副本设置为 loginUser。
     * 这样,在后续的代码中,当前线程就可以通过 threadLocal.get() 方法获取到自己的 loginUser 变量副本,而不会受到其他线程的影响。
     *
     * 通常情况下,我们会将一些需要在整个应用程序中共享的数据存储在一个全局的变量中,
     * 但是这样会存在线程安全问题。使用 ThreadLocal 可以解决这个问题,每个线程都有自己的变量副本,
     * 不会互相干扰。在这段代码中,使用 ThreadLocal 存储了当前线程的登录用户信息,
     * 以便在后续处理请求时能够方便地获取到该信息。
     */
    public static ThreadLocal<LoginUser> threadLocal=new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (Request.HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpStatus.NO_CONTENT.value());
            return true;
        }
        String accessToken = request.getHeader("token");
        if (StringUtils.isBlank(accessToken)) {
            accessToken = request.getParameter("token");
        }
        if (StringUtils.isNotBlank(accessToken)) {
//            token解密
            Claims claims = JWTUtil.claimsJWT(accessToken);
            if (claims == null) {
                //未登录
                CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
                return false;
            }

            Long accountNo = Long.parseLong(claims.get("account_no").toString());
            String headImg = (String) claims.get("head_img");
            String username = (String) claims.get("username");
            String mail = (String) claims.get("mail");
            String phone = (String) claims.get("phone");
            String auth = (String) claims.get("auth");

            LoginUser loginUser = LoginUser.builder()
                    .accountNo(accountNo)
                    .auth(auth)
                    .phone(phone)
                    .headImg(headImg)
                    .mail(mail)
                    .username(username)
                    .build();
            //request.setAttribute("loginUser",loginUser);
            //通过Threadlocal
            threadLocal.set(loginUser);
            return true;
        }
        CommonUtil.sendJsonMessage(response,JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
        return false;

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 释放当前线程的 ThreadLocal 变量副本所占用的内存空间
        threadLocal.remove();
    }
}

拦截器配置类

java 复制代码
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                //添加拦截的路径
                .addPathPatterns("/api/visit_stats/*/**");
                //排除不拦截
//                .excludePathPatterns("/api/product/*/**");
    }
}
相关推荐
骄马之死29 分钟前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端
郑洁文2 小时前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
螺丝钉code3 小时前
JAVA项目 Claude code CLAUDE.md 到底应该怎么写
java·人工智能·claude code
giaz14n9X3 小时前
Redis 分布式锁进阶第五十七篇
数据库·redis·分布式
WyCAGy8ij4 小时前
Redis 分布式锁进阶第二篇讲解
数据库·redis·分布式
摇滚侠4 小时前
Maven 入门+高深 单一架构案例 54-59
java·架构·maven·intellij-idea
VidDown4 小时前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman
折哥的程序人生 · 物流技术专研4 小时前
Java 23 种设计模式:从踩坑到精通 | 原型模式 —— 克隆对象,深拷贝与浅拷贝的坑你踩过吗?
java·设计模式·架构·原型模式·单一职责原则
装不满的克莱因瓶5 小时前
基于 OpenResty 扩展开发实现动态服务注册与发现能力
java·开发语言·架构·openresty
程序员小羊!5 小时前
06Java 异常机制与常用类
java