Token验证登录用户
1. 什么是token
简单来说token是用户登录后生成的凭证,此后用户发出的所有请求的请求头中都要携带此凭证,否则会被拦截器拦截仅能访问放行的部分功能。加入token提高了系统安全性,同时将token信息保存在redis解决了传统储存方式产生的session共享问题
2. 实现
2.1 拦截器部分
首先在mcvconfg中配置拦截器,注意拦截器顺序,刷新token拦截器与验证token拦截器要相邻(否则在验证token时获取id失败)
@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//刷新token(该类由spring管控,可以成功注入redisTemplate)
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);
//验证是否登录
registry.addInterceptor(new LoginCheckFilter()).excludePathPatterns(
//放行路径
"/user/sendCode",
"/user/login",
"/user",
"/user/detail",
"/essay/recommendEssays",
"/essay/hotEssays",
"/essay/find",
"/question/recommendQuestions",
"/question/hotQuestion",
"/question/find",
"/category"
).order(1);
}
刷新token拦截器
@Slf4j
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 {
//跨域处理,cros跨域请求会发起两次请求,放行opinions方式请求容许跨域
if (request.getMethod().equals("OPTIONS")){
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
//获取请求头中token
String token = request.getHeader("Authorization");
String key = LOGIN_TOKEN_KEY + token;
if (StrUtil.isBlank(token)) {
return true;
}
//基于token获取redis中用户
String id = stringRedisTemplate.opsForValue().get(key);
//判断用户是否存在
if (StringUtils.isEmpty(id)) {
return true;
}
//存在,保存用户信息到ThreadLocal(线程池中用于获取用户id)
BaseContext.setMember(Long.valueOf(id));
//刷新token有效期
stringRedisTemplate.expire(key, LOGIN_TOKEN_TIME, TimeUnit.HOURS);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
BaseContext.removeUser();
}
}
验证登陆拦截器
@Slf4j
public class LoginCheckFilter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals("OPTIONS")){
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
if (BaseContext.getUserId() == null){
response.setStatus(401);
log.info("请求被拦截!");
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
BaseContext.removeUser();
}
}
2.2 登录生成token
@Override
public R<LoginDTO> login(Map member) {
//获取邮箱地址
String email = member.get("email").toString();
//获取密码
String password = member.get("password").toString();
//获取验证码
String code = member.get("code").toString();
//从redis中获取验证码
String codeInRedis = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + email);
if (StringUtils.isEmpty(codeInRedis) || !codeInRedis.equals(code)) {
return R.error("验证码错误!");
}
//判断邮箱和密码是否符合规则
if (RegexUtils.isNotEmail(email) || RegexUtils.isNotPassword(password)) {
return R.error("邮箱或密码格式错误");
}
//在数据库中查询用户是否存在
LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Member::getEmail, email);
Member one = this.getOne(queryWrapper);
if (one == null) {
return R.error("该用户未注册!");
}
if (!one.getPassword().equals(password)) {
return R.error("密码错误!");
}
//1.随机生成token,作为登陆令牌
String token = UUID.randomUUID().toString(true);
String keyToken = LOGIN_TOKEN_KEY + token;
//2.将token存入redis
stringRedisTemplate.opsForValue().set(keyToken, one.getId().toString());
//3.设置token刷新时间
stringRedisTemplate.expire(keyToken, LOGIN_TOKEN_TIME, TimeUnit.HOURS);
//用户登录成功删除redis缓存的验证码
stringRedisTemplate.delete(LOGIN_CODE_KEY + email);
LoginDTO loginDTO = new LoginDTO();
loginDTO.setFlag(one.getFlag());
loginDTO.setToken(token);
return R.success(loginDTO);
}