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

优缺点

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/*/**");
    }
}
相关推荐
APIshop14 分钟前
京东关键词搜索接口完全指南
java·开发语言·数据库
东离与糖宝21 分钟前
HashMap从入门到源码:Java7/8/21区别+面试陷阱+高频追问合集
java·人工智能·面试
wang090743 分钟前
Linux性能优化之CPU利用率
java·linux·运维
2601_9498177243 分钟前
Spring+SpringMVC项目中的容器初始化过程
java·后端·spring
做个文艺程序员1 小时前
Spring AI 1.1 三件套实战:Structured Output + Tool Calling + Memory 从踩坑到生产落地
java·大数据·人工智能
云烟成雨TD1 小时前
Spring AI 1.x 系列【21】ToolCallbackProvider 动态工具集成
java·人工智能·spring
辣机小司1 小时前
【生产级 Kafka (KRaft) 双中心容灾演练:MirrorMaker 2.0 (MM2) 核心参数配置与回切踩坑指南】
分布式·kafka·集群同步·kafka双集群
伯恩bourne1 小时前
SpringDoc OpenAPI 3 常用注解详解
java·开发语言
chools1 小时前
Java后端拥抱AI开发之个人学习路线 - - Spring AI【第三期】(向量数据库 + RAG检索增强生成)
java·人工智能·学习·spring·ai
花千树-0102 小时前
Java 实现 ReAct Agent:工具调用与推理循环
java·spring boot·ai·chatgpt·langchain·aigc·ai编程