springboot 实现踢人效果

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
注册配置 MSE Nacos/ZooKeeper,118元/月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 最近被要求修复一个账号可以在不同的浏览器和不同的pc上登录,只要jwt有效,就能访问系统,要求限制用户账号的有效性,简而言之就是当同一个账户登录不同的电脑,后登录的有效,而之前登录的失效。 这里由于是单机部署,不涉及分布式缓存的问题,所以使用单机缓存就可以了,网上看了springboot cache推荐的ganva ,咖啡因,Ehcache,这里我们不使用redis,而网上推荐的这几种说句实话,没看懂如何用。最后发现hutool有一个cacheutil可以用,就是用其中的FIFO实现一下。

最近被要求修复一个账号可以在不同的浏览器和不同的pc上登录,只要jwt有效,就能访问系统,要求限制用户账号的有效性,简而言之就是当同一个账户登录不同的电脑,后登录的有效,而之前登录的失效。

这里由于是单机部署,不涉及分布式缓存的问题,所以使用单机缓存就可以了,网上看了springboot cache推荐的ganva ,咖啡因,Ehcache,这里我们不使用redis,而网上推荐的这几种说句实话,没看懂如何用。最后发现hutool有一个cacheutil可以用,就是用其中的FIFO实现一下。

1.引入hutool依赖

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.9</version>
        </dependency>

2. 设置全局bean

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.FIFOCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CacheConfig {
   
    @Bean
    public FIFOCache<String, Object> globalCache() {
   
        // 初始化并设置缓存容量大小为100
        return CacheUtil.newFIFOCache(100);
    }
}

3.设置web过滤器

# LoginCheckInterceptor.java
public class LoginCheckInterceptor implements HandlerInterceptor {
   
    static Logger log = LoggerFactory.getLogger(LoginCheckInterceptor.class);
    @Autowired
    private FIFOCache<String, Object> globalCache;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        log.info("LoginCheckInterceptor preHandle " + request.getRequestURL());
        Object dLoginUser = request.getSession().getAttribute("USER_INFO");
        UserInfo userinfo=(UserInfo)dLoginUser;
        log.info("checkuser userinfo:"+userinfo.getName());
        String token = request.getHeader("Authorization");
        String salt=(String)globalCache.get(userinfo.getName());
        log.info("salt is:"+salt);

        if ( !JWTTokenUtils.verify(token.replaceAll("Bearer ", ""),salt)) {
   
            // 未登录,转向登录页面!
            JsonHeaderWrapper baseDTO = new JsonHeaderWrapper<>();
            response.setContentType("text/html;charset=UTF-8");
            baseDTO.setErrmsg("Token已失效或用户未登录!");
            PrintWriter writer = response.getWriter();
            Map<String, Object> data = new HashMap<>(2);
            baseDTO.setData(data);
            baseDTO.setStatus(401);
            writer.print(Jackson2Helper.toJsonString(baseDTO));
            return false;
        }else{
   
            UserInfo userInfo=JWTTokenUtils.decode(token);
            log.info("decode userinfo is:"+userInfo.getName());
        }
        return true;
    }

}

@Configuration
public class KongxConfig implements WebMvcConfigurer {
   
    @Value("${portal.exclude.paths:/shell,/index,/authorize/login.do,/inner/monitor/ping,/health/check,/authorize/getUserInfo.do,/authorize/logout.do," +
            "/index.html,/cdn/**,/css/**,/img/**,/js/**,/svg/**,/util/**,/favicon.ico}")
    private String excludePaths;


    @Bean
    public LoginCheckInterceptor loginCheckInterceptor(){
   
        return new LoginCheckInterceptor();
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
   
        argumentResolvers.add(new ServletWebArgumentResolverAdapter(new UserArgumentResolver()));
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
   
        registry.addInterceptor(new LoginValidateInterceptor()).excludePathPatterns(excludePaths.split(","));
        registry.addInterceptor(loginCheckInterceptor()).excludePathPatterns(excludePaths.split(","));
    }
}

在这个过程中发现Spring中的Interceptor拦截器中使用@Autowired注解,在运行时会出现空指针,

在Web开发过程中,我们经常会使用拦截器,做登录权限校验,一般在拦截器里,我们也会用到各种Service方法 。

参考这篇:https://juejin.cn/post/7085246509591035940

4.jwt 设置

import java.util.Date;

public abstract class JWTTokenUtils {
   
    private final static String SECRET = "ADBC";

    //设置jwt失效时间
    private static final long EXPIRATION_TIME_MILLIS = 3600000; // 1 hour

    public static String getToken(UserInfo user) {
   
        String token = "";
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME_MILLIS);
        token = JWT.create().withAudience(Jackson2Helper.toJsonString(user)).withExpiresAt(expirationDate)
                .sign(Algorithm.HMAC256(SECRET));
        return token;
    }

    public static String getToken(UserInfo user,String salt) {
   
        String token = "";
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME_MILLIS);
        token = JWT.create().withAudience(Jackson2Helper.toJsonString(user)).withExpiresAt(expirationDate)
                .sign(Algorithm.HMAC256(salt));
        return token;
    }

    public static UserInfo decode(String token) {
   
        token=token.replace("Bearer","");
        String user = JWT.decode(token).getAudience().get(0);
        return Jackson2Helper.parsonObject(user, new TypeReference<UserInfo>() {
   
        });
    }

    public static boolean verify(String token) {
   
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        try {
   
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
   
            return false;
        }
        return true;
    }


    public static boolean verify(String token,String salt) {
   
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(salt)).build();
        try {
   
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
   
            return false;
        }
        return true;
    }

5.思路

基本思路就是在用户密码校验通过后,随机生成一个salt,使用这个salt生成jwt,由于jwt是无状态的,所以当重新登录会又生成一个salt,这时候 请求拦截发现用现在的salt无法验证上一次的jwt token 就实现了踢人的效果。

相关文章
|
Java Maven Spring
SpringBoot《第二课》(一)
SpringBoot《第二课》(一)
77 0
|
负载均衡 监控 Java
springboot不香吗?为什么还要使用springcloud
springboot不香吗?为什么还要使用springcloud
123 0
|
Java 数据库连接 调度
超神理解:SpringBoot处理定时任务
项目经常会用到定时任务,springboot自然是可以通过整合相关组件来实现的。 目前常用的定时任务的实现有两种: 通过spring 自带的定时器任务@Schedule来实现 通过Quartz来实现
|
XML Java 应用服务中间件
|
前端开发 Java 测试技术
【笑小枫的SpringBoot系列】【十四】SpringBoot发送邮件
【笑小枫的SpringBoot系列】【十四】SpringBoot发送邮件
134 0
SpringBoot学习笔记-12:第十二章-SpringBoot 与任务和邮件
SpringBoot学习笔记-12:第十二章-SpringBoot 与任务和邮件
116 0
SpringBoot学习笔记-12:第十二章-SpringBoot 与任务和邮件
|
XML 存储 开发框架
SpringBoot2.x系列教程01--新纪元之SpringBoot初相见
前言 在上一篇文章中,壹哥 给大家介绍了SpringBoot诞生之前的世界,那时候的世界由Spring主导,负责提供一个供万物生长的土壤,并负责创建万物。但是Spring的世界里,各种生命的创造都很麻烦,需要经历各种流程,大家在这片土地上工作的特别疲累。所以大家都期待着能够出现一个可以让这个世界变得轻松的整合者,所以SpringBoot就来了,为简化而生,向幸福迈进! 接下来,壹哥 就带各位正式认识SpringBoot,看看SpringBoot到底是什么,有什么作用,我们该怎么用他! 一. 概述 当这个世界上大家都在推着独轮车运货的时候,主要就是看谁人多力气大,谁就能多运点东西。但是当汽车产
214 0
|
消息中间件 存储 缓存
|
JSON 前端开发 Java
SpringBoot 实战:一招实现结果的优雅响应
今天说一下 Spring Boot 如何实现优雅的数据响应:统一的结果响应格式、简单的数据封装。
403 0
SpringBoot 实战:一招实现结果的优雅响应