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

优缺点

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/*/**");
    }
}
相关推荐
benbenAItalk8 小时前
数字人口播视频的批量生产实践:素材规范、任务编排与质量验收
java·前端·音视频
sunshine22 girl8 小时前
Java学习二 基本语法2, 算术运算符
java·学习
niucloud-admin8 小时前
JAVA V6 多商户商城 开发文档——插件目录结构
java·开发语言
2601_962885728 小时前
如何用 Python 扫描 A 股跳空缺口并统计缺口回补概率?
java·前端·python
滕州市燕猫虎计算机科技工作室个体工商户8 小时前
Java锁
java·开发语言
隐擎fox10 小时前
高性能网络爬虫架构设计:基于 Python 的长连接复用与分布式会话池调度实践
分布式·python·网络协议·tcp/ip·高并发·网络爬虫、
shehuiyuelaiyuehao10 小时前
1计算机是如何工作的
java·计算机网络
letisgo511 小时前
JAVA 高级进阶07篇《@Transactional失效的8个场景:代理机制到传播行为》
java·面试·transactional·spring事务·aop动态代理
水域安全老周11 小时前
水趣钓鱼救生衣专利拆解:两级锁紧如何解决落水人衣分离
java·前端·网络
三84411 小时前
Java 模板注入(FreeMarker) · 02 · 模板引擎与 FreeMarker 语法
java·开发语言·web安全·freemarker