引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
加配置
spring:
redis:
host: 127.0.0.1 #redis地址
port: 6379 #端口
password: 123456 #密码,无密码可以注释调
database: 10 #库
lettuce:
pool:
max-active: 10 #最大连接数
max-idle: 10 #最多空闲
min-idle: 1 #至少空闲
time-between-eviction-runs: 10s #连接空闲时间
定义登录校验拦截器
/**
* 拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
#校验是否登录,UserHolder中使用ThreadLocal保存用户信息
if(UserHolder.getUser() == null){
response.setStatus(401); //未登录响应401状态码
return false;
}
return true; //放行
}
}
定义刷新token拦截器(只用于刷新token时间)
/**
* 刷新token 拦截器
*/
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate; //LoginInterceptor 是自己创建的不能autowire
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");
if(StrUtil.isBlank(token)){
return true;
}
//从redis中获取该token值
String key = RedisConstants.LOGIN_USER_KEY+token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
if(StrUtil.isBlank(token)){
return true;
}
//将map转Bean
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
UserHolder.saveUser(userDTO);
//刷新token有效期
stringRedisTemplate.expire(key,LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
保存用户信息类:
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}
public static UserDTO getUser(){
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
新建配置类LoginConfig,添加自己定义的拦截器
@Configuration
public class MVCConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns( //添加排除路径
"/user/code",
"/user/login",
).order(1); //order值越小越先执行,值相等则先添加先执行
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);
}
}
登录逻辑
校验手机号 --> 校验验证码 --> 检查用户是否存在 --> 生成token --> 将用户信息user转成Hash存储到redis中 --> 设置token有效期
其中token可用hutool的UUID生成: UUID.randomUUID().toString(true); //true是不要短横线 -
将用户实体转成Map集合,存放到redis中:
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue)-> fieldValue.toString())); //所有字段值转为String
//存redis
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
设置Token有效期
//设置token有效期
stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES); //分钟
手机号验证,可添加一个插件,快捷查询各种校验的正则表达式:
将验证码存入redis并设置验证码有效期
//模拟生成验证码
String code = RandomUtil.randomNumbers(6);
//手机号唯一
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
常量命名规范:
public static final String LOGIN_CODE_KEY = "login:code:"; //验证码的key
public static final Long LOGIN_CODE_TTL = 2L; //验证码的有效期
public static final String LOGIN_USER_KEY = "login:token:"; //token的key
public static final Long LOGIN_USER_TTL = 30L; //token的有效期