您好,登錄后才能下訂單哦!
今天小編給大家分享一下Redis如何實現登錄注冊的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
在傳統的項目中,用戶登錄成功,將用戶信息保存在session中,這種方式在微服務架構中會產生一系列問題。例如在購物車服務具有多臺服務器,當一個請求落在購物車1號服務器后,其session保存了用戶信息,另一個請求落在了購物車2號服務器,發現沒有用戶信息,則重新需要進行登錄。服務器之間有session不共享的問題。為了解決這一問題,tomcat提出了內存拷貝,即只需要配置一些信息即可實現多臺服務器之間的session拷貝,但是這種解決方案也有缺陷,例如:
浪費空間
拷貝有延時,如果在延時內有請求訪問,則還會出現上述問題
為了解決此類問題,我們需要使用多個服務共享的信息平臺,例如Redis
直接上流程圖
流程圖簡潔明了,其中需要注意的是
Redis中存入驗證碼的key是手機號拼接的字符串,為什么保存用戶到Redis的key要使用隨機token,而不是手機號拼接的字符串呢?
因為在用戶登錄注冊時,服務器會獲取到手機號,所以可以使用手機號作為key,進行驗證手機號和驗證碼時也方便進行匹對,那么在保存用戶信息到Redis時為什么要使用隨機token呢?因為在用戶獨立成功后,用戶的每次請求都會攜帶cookie,如果將保存用戶信息的key設置為含手機號的,那么用戶的請求中的cookie也需要攜帶手機號,這樣就會有一定的安全風險,所以在用戶登錄成功后,我們隨機生成token,用token作為key,并且返回給前端token,這樣前端請求時就會攜帶token,也避免了安全隱患。
@Override public Result sedCode(String phone, HttpSession session) { //1. 校驗手機號 if (RegexUtils.isPhoneInvalid(phone)) { //2.如果不符合,返回錯誤信息 return Result.fail("手機號格式錯誤"); } // 3.從redis里獲取驗證碼是否存在 if(null==stringRedisTemplate.opsForValue().get("loginCode" + phone)){ log.info("請勿重復獲取驗證碼"); return Result.fail("請勿重復獲取驗證碼"); } //4. 符合,生成驗證碼 String code = RandomUtil.randomNumbers(6); //5. 保存驗證碼到redis,并設置有效期1分鐘,在設置key的時候,可以提前設置一個常量,然后在這里引用即可 stringRedisTemplate.opsForValue().set("loginCode:"+phone,code,1, TimeUnit.MINUTES); //5. 發送驗證碼 模擬發送 log.debug("發送短信驗證碼成功,驗證碼:{}",code); //返回ok return Result.ok(); }
@Override public Result login(LoginFormDTO loginForm, HttpSession session) { //1. 校驗手機號 String phone = loginForm.getPhone(); if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("手機號格式錯誤"); } //2. 獲取Redis中的校驗驗證碼 String cacheCode = stringRedisTemplate.opsForValue().get("loginCode" + phone); // 3.獲取表單中的驗證碼 String code = loginForm.getCode(); if (cacheCode == null || !cacheCode.toString().equals(code)){ //3. 不一致,報錯 return Result.fail("驗證碼錯誤"); } //4.一致,根據手機號查詢用戶 User user = query().eq("phone", phone).one(); //5. 判斷用戶是否存在 if (user == null){ //6. 不存在,創建新用戶 user = createUserWithPhone(phone); } //7.保存用戶信息到session // 生成token String token = UUID.randomUUID().toString(); // 將User轉為Map UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); Map<String, Object> userDtoMap = BeanUtil.beanToMap(userDTO); // 存儲 stringRedisTemplate.opsForHash().putAll("login:token:"+token,userDtoMap); // 設置有效期30分鐘 stringRedisTemplate.expire("login:token:"+token,30, TimeUnit.MINUTES); // 返回token給前端 return Result.ok(token); }
有些請求是需要用戶登錄才能進行訪問的,所以我們設置一個登錄攔截器先攔截請求,判斷用戶是否登錄,如果登錄了就進行放行即可。
2.3.1 實現HandlerInterceptor類
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 { // 1.獲取請求頭中的token String token = request.getHeader("authorization"); if (StrUtil.isBlank(token)) { return true; } // 2.基于TOKEN獲取redis中的用戶 String key = "login:token:" + token; Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key); // 3.判斷用戶是否存在 if (userMap.isEmpty()) { return true; } // 5.將查詢到的hash數據轉為UserDTO UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); // 6.存在,保存用戶信息到 ThreadLocal UserHolder.saveUser(userDTO); // 7.刷新token有效期 stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES); // 8.放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 移除用戶 UserHolder.removeUser(); } }
preHandle方法是在controller之前運行,在這個方法里面可以進行驗證登錄狀態的操作。
為什么要將用戶保存到ThreadLocal?因為每一個線程都是獨立的,如果將用戶信息保存到公共變量中,會造成線程安全問題,每一個線程都具備一個ThreadLocal內存,我們將用戶信息保存到ThreadLocal中即可實現線程獨享一份用戶信息
為什么要刷新Redis中用戶信息的有效時長?因為在session中,其機制是當用戶不在使用session中的數據超過30分鐘就會剔除session的數據,所以在攔截器的前置攔截中進行刷新即可
上述代碼的第三步驟,為什么userMap為空了還要放行呢?因為這個攔截器只是做Redis用戶信息刷新存活時間的功能,真正攔截的是LoginInterceptor,LoginInterceptor代碼在下面展示
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // ThreadLocal中獲取用戶信息 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(); } }
兩個攔截器配置了,但是沒有生效,需要在配置類里進行配置
@Configuration public class MvcConfig implements WebMvcConfigurer { @Autowired private StringRedisTemplate redisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { // 登錄攔截器 registry.addInterceptor(new LoginInterceptor()) .excludePathPatterns( // 配置不需要被攔截的路徑 "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ).order(1); registry.addInterceptor(new RefreshTokenInterceptor(redisTemplate)) .excludePathPatterns( "/user/login", "/user/code" ).order(0); } }
這里配置兩個攔截器,兩個攔截器是有先后順序的,上述已經說明,通過設置order屬性即可配置先后順序,值越小,優先級越高。
以上就是“Redis如何實現登錄注冊”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。