概述
其实就是以用户最后一次登录的为准。其他登录的地方全部提示:你已经下线,是否重新登录。从而保护你的操作信息是安全的。
原理
- 每登录一次产生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;
}
}