springboot 实现踢人效果

简介: 最近被要求修复一个账号可以在不同的浏览器和不同的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 就实现了踢人的效果。

相关文章
|
缓存 UED 开发者
HTTP常用状态码详解
HTTP常用状态码详解
|
NoSQL Java Redis
SpringBoot集成Redis报non null key required(已解决)
SpringBoot开发过程中遇到的问题并记录
1865 0
SpringBoot集成Redis报non null key required(已解决)
|
运维 监控 持续交付
微服务架构解析:跨越传统架构的技术革命
微服务架构(Microservices Architecture)是一种软件架构风格,它将一个大型的单体应用拆分为多个小而独立的服务,每个服务都可以独立开发、部署和扩展。
3283 36
微服务架构解析:跨越传统架构的技术革命
|
JSON Java Maven
关于使用Java-JWT的笔记
这篇文章介绍了使用Java-JWT库来生成和验证JSON Web Tokens (JWT) 的方法。文中解释了JWT的组成,包括头部、载荷和签名,并提供了如何使用java-jwt库生成和验证token的示例代码。此外,还提供了Maven依赖和一些关于token的标准声明和自定义声明的解释。
关于使用Java-JWT的笔记
|
11月前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
5523 13
Spring Boot 3 集成 Spring Security + JWT
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
1008 2
|
监控 数据挖掘 测试技术
微服务架构
微服务架构
180 1
|
消息中间件 监控 数据可视化
Apache Airflow 开源最顶级的分布式工作流平台
Apache Airflow 是一个用于创作、调度和监控工作流的平台,通过将工作流定义为代码,实现更好的可维护性和协作性。Airflow 使用有向无环图(DAG)定义任务,支持动态生成、扩展和优雅的管道设计。其丰富的命令行工具和用户界面使得任务管理和监控更加便捷。适用于静态和缓慢变化的工作流,常用于数据处理。
Apache Airflow 开源最顶级的分布式工作流平台
|
JSON Java API
优雅地进行全局异常处理、统一返回值封装、自定义异常错误码——Graceful-Response推荐
Graceful Response是一个Spring Boot体系下的优雅响应处理器,提供一站式统一返回值封装、全局异常处理、自定义异常错误码等功能,使用Graceful Response进行web接口开发不仅可以节省大量的时间,还可以提高代码质量,使代码逻辑更清晰。
536 0
|
存储 缓存 JSON
详解HTTP四种请求:POST、GET、DELETE、PUT
【4月更文挑战第3天】
72016 5
详解HTTP四种请求:POST、GET、DELETE、PUT