0 目录

1 基于session实现登录
1.1 发送短信验证码

即:将生成的验证码保存到session中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Override public Result sendCode(String phone, HttpSession session) { if(RegexUtils.isPhoneInvalid(phone)){ return Result.fail("手机号格式错误!"); } String code = RandomUtil.randomNumbers(6); session.setAttribute("code",code); log.debug("发送短信验证码成功,验证码:{}"+code); return Result.ok(); }
|
1.2 短信验证码注册登录

即:校验完成验证码之后,根据用户手机号去数据库查询,查询用户如果不存在,那么就创建用户,将创建的心用户保存到数据库中;如果查询到用户存在,那么将用户信息保存到session中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Data public class LoginFormDTO { private String phone; private String code; private String password; }
@Override public Result login(LoginFormDTO loginForm, HttpSession session) { String phone = loginForm.getPhone(); if(RegexUtils.isPhoneInvalid(phone)){ return Result.fail("手机号格式错误!"); } Object cacheCode = session.getAttribute("code"); String code = loginForm.getCode(); if(cacheCode==null ||! cacheCode.toString().equals(code)){ return Result.fail("验证码错误!"); } User user = query().eq("phone", phone).one(); if(user==null) { user=createUsrWithPhone(phone); } session.setAttribute("user",user); return null; }
private User createUsrWithPhone(String phone) { User user = new User(); user.setPhone(phone); user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10)); save(user); return user; }
|
1.3 校验登录状态

即:从拦截器拦截请求之后,从request中获取到cookie,由cookie获取session进而获取到用户信息,此时判断用户是否存在,如果用户不存在进行拦截,用户存在就将用户数据保存到ThreadLocal中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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(); } }
|
2 登录校验拦截器
2.1 拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("user"); if(user==null) { response.setStatus(401); return false; } UserHolder.saveUser((UserDTO) user); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); } }
|
2.2 添加拦截器
1 2 3 4 5 6 7 8
| @Configuration public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/voucher/**","/upload/**"); } }
|
3 session共享问题
session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题

4 Redis替换session业务

5 基于Redis实现短信登录
5.1 发送验证码
流程修改:生成验证码后,将验证码放入至redis并且设置过期时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Override public Result sendCode(String phone, HttpSession session) { if(RegexUtils.isPhoneInvalid(phone)){ return Result.fail("手机号格式错误!"); } String code = RandomUtil.randomNumbers(6); stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES); log.debug("发送短信验证码成功,验证码:{}"+code); return Result.ok(); }
|
5.2 验证码登录
修改:校验的时候,客户端传入的验证码和redis中的验证码进行比对,随后需要将用户信息保存到redis中,此时选择使用Hash结构存储用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Override public Result login(LoginFormDTO loginForm, HttpSession session) { String phone = loginForm.getPhone(); if(RegexUtils.isPhoneInvalid(phone)){ return Result.fail("手机号格式错误!"); } String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone); String code = loginForm.getCode(); if(cacheCode==null ||!cacheCode.equals(code)){ return Result.fail("验证码错误!"); } User user = query().eq("phone", phone).one(); if(user==null) { user=createUsrWithPhone(phone); } String token = UUID.randomUUID().toString(true); UserDTO userDTO=BeanUtil.copyProperties(user,UserDTO.class); Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString())); String tokenKey=LOGIN_USER_KEY+token; stringRedisTemplate.opsForHash().putAll(tokenKey,userMap); stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES); return Result.ok(); }
private User createUsrWithPhone(String phone) { User user = new User(); user.setPhone(phone); user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10)); save(user); return user; }
|
5.3 拦截器修改
修改:此时获取的用户信息是从redis中获取而不是从session获取。查询到用户信息之后将Hash数据转为UserDto对象,再存入ThreadLocal中,同时刷新token的有效期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class LoginInterceptor implements HandlerInterceptor { private StringRedisTemplate stringRedisTemplate; public LoginInterceptor(StringRedisTemplate stringRedisTemplate){ this.stringRedisTemplate=stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("authorization"); if(StrUtil.isBlank(token)){ response.setStatus(401); return false; } String key=LOGIN_USER_KEY+token; Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key); if(userMap.isEmpty()){ response.setStatus(401); return false; } UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); UserHolder.saveUser(userDTO); 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(); } }
|
6 登录状态刷新问题

即:配置两个拦截器
拦截器1:拦截一切的请求,同时刷新token的有效期
拦截器2:拦截登录的请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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 {
String token = request.getHeader("authorization"); if(StrUtil.isBlank(token)){ return true; } String key=LOGIN_USER_KEY+token; Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key); if(userMap.isEmpty()){ return true; } UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); UserHolder.saveUser(userDTO); 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(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class LoginInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(UserHolder.getUser()==null){ response.setStatus(401); return false; } return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @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", "/blog/hot", "/shop/**", "/shop-type/**", "/voucher/**", "/upload/**").order(1); registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0); } }
|
拦截器的顺序,order值越小越小执行