Redis(2)短信验证码登录
Redis實現短信驗證碼登錄
- 登錄流程
- 1.發送驗證碼:
- 2.短信驗證碼登錄,注冊
- 3.校驗登錄狀態
- 解決狀態登錄刷新問題
redis指令參考:https://blog.csdn.net/weixin_43994244/article/details/127489311
redis安裝:https://blog.csdn.net/weixin_43994244/article/details/123111671?spm=1001.2014.3001.5502
阿里云短信認證參考:https://blog.csdn.net/weixin_46931860/article/details/124512435
1.session共享問題:
多臺Tomcat并不共享session存儲空間,當請求切換到不同Tomcat服務時導致數據丟失的問題
2.session拷貝問題:
1、每臺服務器中都有完整的一份session數據,服務器壓力過大
2、session拷貝數據時,可能會出現延遲
3.Redis代替session需要考慮的問題:
1、選擇合適的數據結構
2、選擇合適的key
3、選擇合適的存儲粒度
4.Redis的key滿足條件:
1、key要具有唯一性
2、key要方便攜帶
登錄流程
1.發送驗證碼:
1.用戶提交手機號,校驗手機號是否合法。
2.如果不合法,則要求用戶重新輸入手機號,如果手機號合法,后臺此時生成對應的驗證碼
3.保存驗證碼到redis中,手機號作為key(只在緩存中使用,數據結構簡單)
4.通過短信的方式將驗證碼發送給用戶
2.短信驗證碼登錄,注冊
1.用戶輸入驗證碼和手機號,后臺從redis中根據key拿到當前驗證碼
2.和用戶輸入的驗證碼進行校驗,不一致,則無法通過校驗;一致,則后臺根據手機號查詢用戶
3.如果用戶不存在,則為用戶創建賬號信息,保存到數據庫
4.將用戶信息保存到redis中,隨機串token作為key(返回給前端,不能存儲敏感信息),方便后續獲得當前登錄信息
相關類數據:
User:
LoginFormDTO:
@Data public class LoginFormDTO {private String phone;private String code;private String password; }UserDTO:
@Data @ToString public class UserDTO {private Long id;private String nickName;private String icon; }登錄信息校驗正則表達式:
public abstract class RegexPatterns {/*** 手機號正則*/public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";/*** 郵箱正則*/public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";/*** 密碼正則。4~32位的字母、數字、下劃線*/public static final String PASSWORD_REGEX = "^\\w{4,32}$";/*** 驗證碼正則, 6位數字或字母*/public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";}校驗工具類:
public class RegexUtils {/*** 是否是無效手機格式* @param phone 要校驗的手機號* @return true:符合,false:不符合*/public static boolean isPhoneInvalid(String phone){return mismatch(phone, RegexPatterns.PHONE_REGEX);}/*** 是否是無效郵箱格式* @param email 要校驗的郵箱* @return true:符合,false:不符合*/public static boolean isEmailInvalid(String email){return mismatch(email, RegexPatterns.EMAIL_REGEX);}/*** 是否是無效驗證碼格式* @param code 要校驗的驗證碼* @return true:符合,false:不符合*/public static boolean isCodeInvalid(String code){return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);}// 校驗是否不符合正則格式private static boolean mismatch(String str, String regex){if (StrUtil.isBlank(str)) {return true;}return !str.matches(regex);} }3.校驗登錄狀態
用戶在請求時候,會從攜帶token到后臺,后臺通過token從redis中拿到用戶信息,如果沒有用戶信息,則進行攔截,如果有用戶信息,則將用戶信息保存到threadLocal中,并且放行。
備注:threadLocal中,無論是他的put方法和他的get方法, 都是先從獲得當前用戶的線程,然后從線程中取出線程的成員變量map,只要線程不一樣,map就不一樣,所以可以通過這種方式來做到線程隔離。
1.編寫攔截器
public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.獲取請求頭中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 2.基于TOKEN獲取redis中的用戶String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判斷用戶是否存在if (userMap.isEmpty()) {return true;}// 5.將查詢到的hash數據轉為UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用戶信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用戶UserHolder.removeUser();} }2.配置攔截器
@Configuration public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;//添加登錄攔截器,設置攔截路徑@Overridepublic void addInterceptors(InterceptorRegistry registry) {//登錄攔截器registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))//配置攔截路徑.excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");} }3.ThreadLocal工具類
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();} }解決狀態登錄刷新問題
問題:上述攔截器只是攔截需要被攔截的路徑,假設當前用戶訪問了一些不需要攔截的路徑,此攔截器就不會生效,所以此時令牌刷新的動作實際上就不會執行,token仍會過期
解決方案:添加一個攔截器,在第一個攔截器中攔截所有的路徑,把第二個攔截器做的事情放入到第一個攔截器中,同時刷新令牌,因為第一個攔截器有了threadLocal的數據,所以此時第二個攔截器只需要判斷攔截器中的user對象是否存在即可,完成整體刷新功能。
RefreshTokenInterceptor
/*** 攔截一切路徑,刷新token時間*/ public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("authorization");//放行if(StrUtil.isBlank(token)){return true;}//2.基于token獲取redis中的用戶Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY+token);//3.判斷用戶是否存在if(map.isEmpty()){//4.不存在,攔截,返回401狀態碼return true;}//5.將查詢到的數據轉為userDto,保存到ThreadLocal中UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);UserHolder.saveUser(userDTO);//7.刷新token有效期stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,30, TimeUnit.MINUTES);//8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}}LoginInterceptor
/*** 攔截需要登錄的路徑*/ public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//用戶訪問一直不需要登錄的頁面,攔截器不生效,不刷新token有效期//則添加一個 RefreshTokenInterceptor攔截一切路徑,刷新token時間if(UserHolder.getUser() == null){//沒有,需要攔截response.setStatus(401);return false;}//有用戶,則放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}}攔截器配置
@Configuration public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;//添加登錄攔截器,設置攔截路徑@Overridepublic void addInterceptors(InterceptorRegistry registry) {//默認順序為添加順序//token刷新攔截器,攔截所有請求,先執行registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);//登錄攔截器registry.addInterceptor(new LoginInterceptor())//配置攔截路徑.excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");} }總結
以上是生活随笔為你收集整理的Redis(2)短信验证码登录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嵌入式linux添加字体,嵌入式字体编辑
- 下一篇: 【Redis】Redis 分片集群的渐进