使用Jwt手动实现多端设备登录挤下线

512 阅读2分钟

概述

其实就是以用户最后一次登录的为准。其他登录的地方全部提示:你已经下线,是否重新登录。从而保护你的操作信息是安全的。

原理

  • 每登录一次产生uuid并将其存入redis中,每次请求时通过比较客户端与服务器中的uuid是否一致,不一致则说明有登录行为。
  • 只能一个地方登录key = sys:login:+userid如果同设备互斥key = “sys:login:“+pc+”:“+userid

具体实现

1.生成uuid

package com.zl.springbootssm.controller.login;


@RestController
@Slf4j
@Api(tags = "登录业务")
public class LoginController {
    @Autowired
    @Qualifier("userServiceImp")
    private IUserService userService;
    @Autowired
    private JwtService jwtService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 登录
     *
     * @param userVo
     * @return
     */
    @PostMapping("/login/toLogin")
    @IgnoreToken
    public UserBo logined(@RequestBody UserVo userVo) {
        // 这里有校验,spring-validator框架来完成 或者用断言 或者用自己封装的
        //可以解决if/else问题
        PugAssert.isEmptyEx(userVo.getUserName(), AdminUserResultEnum.USER_NAME_NOT_EMPTY);
        PugAssert.isEmptyEx(userVo.getPassword(), AdminUserResultEnum.USER_PWD_NOT_EMPTY);
        // 根据用户名称查询用户信息
        User dbLoginUser = userService.login(userVo);
        PugAssert.isNullEx(dbLoginUser, AdminUserResultEnum.USER_NULL_ERROR);
        // 用户输入的密码
        String inputPwd = MD5Util.md5slat(userVo.getPassword());
        // 如果输入密码和数据库密码不一致
        boolean isLogin = dbLoginUser.getPassword().equalsIgnoreCase(inputPwd);
        // 如果输入的账号和有误,isLogin=false.注意isFalseEx在里面取反的,所以会抛出异常
        PugAssert.isFalseEx(isLogin,AdminUserResultEnum.USER_INPUT_USERNAME_ERROR);
        UserBo userBo = new UserBo();
        // 根据用户生成token
        String token = jwtService.createToken(dbLoginUser.getId());
        userBo.setToken(token);
        // 注意把一些敏感信息全部清空返回
        dbLoginUser.setPassword(null);
        userBo.setUser(dbLoginUser);
        //生成新的uuid--覆盖redis中tokenuuid的值。
        String tokenUuid= UUID.randomUUID().toString();
        String tokenUuidKey=USER_LOGIN_LOGOUT_KEY+dbLoginUser.getId();
        stringRedisTemplate.opsForValue().set(tokenUuidKey,tokenUuid);
        userBo.setTokenUuid(tokenUuid);
        return userBo;
    }
}

2.拦截请求比对uuid的值

@Component
@Slf4j
public class LogoutInterceptor implements HandlerInterceptor, AdminRedisKeyManager {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private JwtService jwtService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        /*********************这里是用户输入的信息********************/
        // 获取用户传递过来的tokenuuid
        String tokenUuid = request.getHeader(TOKEN_UUID_NAME);
        // 如果没有获取到,说明没有登录
        Vsserts.isEmptyEx(tokenUuid, AdminUserResultEnum.USER_LOGIN_UUID_EMPTY);
        // *******************从redis获取uuid********************/
        String tokenUserId = request.getHeader(TOKEN_USERID_NAME);
        String tokenUuidKey = USER_LOGIN_LOGOUT_KEY + tokenUserId;
        String cacheUuid = stringRedisTemplate.opsForValue().get(tokenUuidKey);
        // 如果没有获取到,说明没有登录
        Vsserts.isEmptyEx(tokenUuid, AdminUserResultEnum.USER_LOGIN_UUID_EMPTY);
        // *******************比较********************/
        // 如果你当前访问的uuid和缓存的uuid不同,就说明你在别的地方登录了。
        if (!tokenUuid.equalsIgnoreCase(cacheUuid)) {
            throw new PugValidatorException(AdminUserResultEnum.USER_LOGIN_SAME);
        }
        response.setHeader("tokenUuid",tokenUuid);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserThreadLocal.remove();
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserThreadLocal.remove();
    }
}

3.注册和设置规则



/**
 * Description:
 * Author: Mr.Zhao
 * Create Date Time: 2023/11/17 15:47.
 * Update Date Time:
 */
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Autowired
    public Interceptor interceptor;
    @Autowired
    public LogoutInterceptor logoutInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //判断是否多端登录     registry.addInterceptor(logoutInterceptor).addPathPatterns("/**").excludePathPatterns("/login/**");
        //token校验     registry.addInterceptor(interceptor).addPathPatterns("/order/**").excludePathPatterns("/login/**");

    }
    @Bean
    public Order inject() {
        Order order = new Order();
        order.setNumber(111);
        return order;
    }
}