springboot 实现踢人效果

本文涉及的产品
性能测试 PTS,5000VUM额度
应用实时监控服务-应用监控,每月50GB免费额度
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 最近被要求修复一个账号可以在不同的浏览器和不同的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 就实现了踢人的效果。

相关文章
|
8月前
|
网络协议 前端开发 Java
springboot整合websorket推送消息实战
springboot整合websorket推送消息实战
64 0
|
Dubbo Java 测试技术
【SpringBoot学习笔记 十二】SpringBoot异步任务、定时任务、邮件任务
【SpringBoot学习笔记 十二】SpringBoot异步任务、定时任务、邮件任务
157 0
|
负载均衡 监控 Java
springboot不香吗?为什么还要使用springcloud
springboot不香吗?为什么还要使用springcloud
136 0
|
消息中间件 Java 微服务
还在用 OpenFeign?来试试 SpringBoot3 中的这个新玩意!
还在用 OpenFeign?来试试 SpringBoot3 中的这个新玩意!
1609 0
|
前端开发 Java 关系型数据库
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(二)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(二)
|
前端开发 Java 测试技术
【笑小枫的SpringBoot系列】【十四】SpringBoot发送邮件
【笑小枫的SpringBoot系列】【十四】SpringBoot发送邮件
147 0
|
XML JSON 前端开发
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(一)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(一)
|
前端开发 JavaScript Java
基于springboot的通知反馈系统
该系统创作于2022年4月,包含详细数据库设计。基于springboot技术,数据层为MyBatis,mysql数据库,页面使用html,具有完整的业务逻辑,适合选题:通知、消息通知、通知反馈、部门信息收集等。
基于springboot的通知反馈系统
|
前端开发 JavaScript Java
基于SpringBoot的答题系统
该系统框架后端采用Springboot、Mybatis框架,前端使用templates、bootstrap、layUI、WeAdmin、css、js等,适合做类似答题系统,要求界面美观的同学。主要功能包括:答题、提问、积分商城、资料管理、意见反馈等,以及管理员管理相关功能。
基于SpringBoot的答题系统