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

优缺点

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/*/**");
    }
}
相关推荐
blammmp4 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵22 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong27 分钟前
Java反射
java·开发语言·反射
九圣残炎1 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge1 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚1 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐2 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。2 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野2 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航2 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot