Redis實現(xiàn)短信登錄的示例代碼
一、基于Session實現(xiàn)登錄
---------------------------------------------------Controller @PostMapping("code") public Result sendCode(@RequestParam("phone") String phone, HttpSession session) { //發(fā)送短信驗證碼并保存驗證碼 return userService.sendCode(phone,session); } @PostMapping("/login") public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){ //實現(xiàn)登錄功能 loginForm 登錄參數(shù),包含手機號、驗證碼;或者手機號、密碼 return userService.login(loginForm,session); } @GetMapping("/me") public Result me(){ // 獲取當前登錄的用戶并返回 UserDTO user = UserHolder.getUser(); return Result.ok(user); } ---------------------------------------------------Service @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @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(); } @Override public Result login(LoginFormDTO loginForm, HttpSession session) { if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){ return Result.fail("手機號格式錯誤"); } Object catchCode = session.getAttribute("code"); String code = loginForm.getCode(); if(catchCode == null || !catchCode.equals(code)){ Result.fail("驗證碼錯誤"); } User user = query().eq("phone", loginForm.getPhone()).one(); if (user == null) { user = createUserWithPhone(loginForm.getPhone()); } session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class)); return Result.ok(); } private User createUserWithPhone(String phone) { User user = new User(); user.setPhone(phone); user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomNumbers(3)); save(user); return user; } } ---------------------------------------------------自定義攔截器 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(); } } ---------------------------------------------------添加攔截器并指定攔截的請求和不攔截的請求 @Configuration public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //實現(xiàn)WebMvcConfigurer接口中的addInterceptors方法把自定義的攔截器類添加進來即可 registry.addInterceptor(new LoginInterceptor()).excludePathPatterns( "/user/code", "/user/login", "/blog/hot", "/shop/**", "/shop-type/**", "/voucher/**" ); } }
總結: 短信驗證碼用隨機工具生成6位數(shù),保存了session
中,在用戶使用手機號登錄時,獲取session
中的驗證碼和請求參數(shù)中的驗證碼比對,一致則去庫里查該手機號的用戶是否存在,不存在則新建用戶,并把該用戶對象存在在session
中。校驗登錄狀態(tài)是使用HandlerInterceptor
攔截器實現(xiàn)的,在此之前需要配置攔截哪些請求,不攔截哪些請求,從客戶端的請求中獲取session
信息,為空返回401狀態(tài),不為空則把用戶信息存儲在ThreadLocal
中,在請求處理完之后銷毀用戶信息。
問題: 集群的session共享問題。
多臺Tomcat并不共享session存儲空間,當請求切換到不同Tomcat服務時導致數(shù)據(jù)丟失的問題。早期的Tomcat提供session拷貝功能,但是并不能解決問題,問題有1.多臺Tomcat保存相同的數(shù)據(jù)信息,內(nèi)存空間浪費;2.拷貝需要時間,在延遲之內(nèi)有用戶訪問,多臺Tomcat依然存在數(shù)據(jù)不一致。
二、基于Redis實現(xiàn)共享Session實現(xiàn)登錄
-------------------只記錄有變化的--------------------------------Service @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Resource private StringRedisTemplate stringRedisTemplate; @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(); } @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 = createUserWithPhone(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())); stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, userMap); stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES); return Result.ok(token); } } ---------------------------------------------------添加攔截器并指定攔截的請求和不攔截的請求 @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/**" ).order(1); registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0); } } ---------------------------------------------------自定義攔截器 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; } } public class RefreshTokenInterceptor implements HandlerInterceptor { private 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; } Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token); if (userMap.isEmpty()) { return true; } UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); //保存在ThreadLocal中 UserHolder.saveUser(userDTO); //刷新token有效期 stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); } public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } }
總結: 短信驗證碼修改為以手機號為key驗證碼為value
保存在redis
中,在用戶使用手機號登錄時,獲取redis
中的驗證碼和請求參數(shù)中的驗證碼比對,一致則去庫里查該手機號的用戶是否存在,不存在則新建用戶,并把該用戶對象存在在redis
中。校驗登錄狀態(tài)是使用HandlerInterceptor
攔截器實現(xiàn)的,在此之前需要配置攔截哪些請求,不攔截哪些請求,從客戶端的請求頭中獲取token信息,并從redis中獲取用戶信息, 為空返回401狀態(tài),不為空則把用戶信息存儲在ThreadLocal
中,還把驗證碼和用戶信息設置有效時間,時間一過,則退出用戶登錄。在請求處理完之后銷毀用戶信息。
改造的點:
- 發(fā)送短信驗證碼時,key使用手機號來確保唯一存儲在redis中,在用戶登錄時可以根據(jù)key來取出驗證碼校對。
- 短信驗證碼登錄時,key使用UUID來確保唯一存儲在redis中,為確保將來前端能把token發(fā)送過來進行校驗,在請求結束前把token返回給客戶端。
問題:
1.攔截器能否真正的實現(xiàn)只要用戶一直在訪問,token就不會過期?
不行,因為攔截器只攔截需要登錄的路徑,如果用戶在有效期內(nèi)一直訪問不需要登錄的路徑,那么redis中的token就會過期。問題解決如下圖。
到此這篇關于Redis實現(xiàn)短信登錄的示例代碼的文章就介紹到這了,更多相關Redis 短信登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Redis Redisson lock和tryLock的原理分析
這篇文章主要介紹了Redis Redisson lock和tryLock的原理分析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04websocket+redis動態(tài)訂閱和動態(tài)取消訂閱的實現(xiàn)示例
本文主要介紹了websocket+redis動態(tài)訂閱和動態(tài)取消訂閱,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-05-05