Spring Boot+Redis实现用户登录验证的思路
!!!
·
一、为什么进行登录验证
进行登录验证的作用,是为了保护数据,在用户没有进行登录时有些数据和操作是不能被授权使用的。例如,购物商城里的评论,未登录时可以看评论但是不能写评论。也不可以买东西。这就要求我们进行每一个请求的登录验证了。
二、实现思路
实现登录验证我知道的方法有两种,第一种是存入session中,但是session只能保证一次会话有效,就是说在同一个Tomcat中有效,但是我们为了减少服务器的压力,通常会配置一个tomcat的集群,这时一个tomcat中session可以在另一个tomcat中被查询到吗?肯定是不行的。
所以,有了第二种方法,存入redis中。每个tomcat服务都可以通过redis进行校验。具体过程如下(前面还有验证码校验省略了,道理一样):
1.首先进行用户登录,先在数据库中查,如果存在用户则将用户信息存入redis中,并设置有效时间:
// 修改: 将信息存入redis中,并随机生成token登录令牌,将User对象转为hash格式存储
// 随机生成UUID作为token
String token = UUID.randomUUID().toString(true);
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 将userDTO转成hashMap,userDTO中的Long类型不能转化为String,所以需要我们自己进行转化
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
// 将map中的值都变成了字符串类型
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
HashOperations<String, Object, Object> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash();
stringObjectObjectHashOperations.putAll(LOGIN_USER_KEY + token, userMap);
// 设置有效时间,问题:该方式说明无论你是否操作一但过了30分钟,就会被认定为未登录,所以我们应该在拦截器中设置每次操作更新token的存活时间
stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
// 返回token给浏览器
return Result.ok(token);
2.如果在数据库中没有说明第一次登录,则直接进行注册
private User createUserWithPhone(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(6));
this.save(user);
return user;
}
三、出现的问题
1.设置了有效时间为30分钟实现,但是这样写表示无论怎样操作我们用户登录验证的有效时间只有30分钟,时间一过我们就会被强制登出。
2.如何实现对请求的登录验证呢?
四、解决问题
问题1
每次请求操作我们都更新一次redis中用户信息的存活时间
// 新增刷新token的有效期
stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
问题2:
我们可以自定义拦截器,进行路径的拦截,将自定义拦截器,加入到springMVC的默认拦截器中,并且可以通过order设置使用顺序,数值越小越先使用:
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Autowired
StringRedisTemplate redisTemplate;
/**
* @Description 添加自定义的拦截器,并排除不需要拦截的请求
* @Retrurn
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO: 暂时将upload放行,方便测试
// 登录拦截器
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/upload/**",
"/voucher/**"
).order(1);
// token刷新拦截器
registry.addInterceptor(new RefreshtokenInterceptor(redisTemplate)).order(0);
}
}
五、对两个拦截器的说明
一、LoginInterceptor
主要用来进行登录验证的拦截
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 只用进行判断是否需要拦截,依据就是ThreadLocal中是否存在用户,有就放行,没有就拦截
UserDTO user = UserHolder.getUser();
if (user == null){
response.setStatus(401);
return false;
}
// 放行
return true;
}
/**
* @Description 后置拦截器,前置拦截器放行后,删除session保护用户信息
* @Retrurn
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
二、RefreshtokenInterceptor
在登录拦截器前进行全路径拦截,为了更新redis中的用户信息存活时间,以及将用户信息存入ThreadLocal中便于使用。
public class RefreshtokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshtokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求头中的token
String token = request.getHeader("authorization");
boolean blank = StrUtil.isBlank(token);
// 是空表示没有登录,放行交给登录拦截器
if (blank) {
return true;
}
// 基于token获取redis中的用户,entries可以接受一个键值中的所有字段
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
// 取不到也放行,该拦截器只是为了更新redis和保存ThreadLocal
if (userMap.isEmpty()) {
return true;
}
// 如果不为空,则将usermap在转换成userDTO
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 存在放行,并且保存在ThreadLocal中
UserHolder.saveUser(userDTO);
// 新增刷新token的有效期
stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
// 放行
return true;
}
}
更多推荐
已为社区贡献2条内容
所有评论(0)