如何基于Session實(shí)現(xiàn)短信登錄功能
一、基于Session實(shí)現(xiàn)登錄
1.1 業(yè)務(wù)流程圖
二、發(fā)送短信驗(yàn)證碼
2.1 發(fā)送短信請(qǐng)求方式及參數(shù)說明
這個(gè)地方為什么需要session? 因?yàn)槲覀冃枰羊?yàn)證碼保存在session當(dāng)中
/** * 發(fā)送手機(jī)驗(yàn)證碼 */ @PostMapping("code") public Result sendCode(@RequestParam("phone") String phone, HttpSession session) { // TODO 發(fā)送短信驗(yàn)證碼并保存驗(yàn)證碼 // return Result.fail("功能未完成"); return userService.sendCode(phone,session); }
2.2 業(yè)務(wù)層代碼模擬發(fā)送短信
@Override public Result sendCode(String phone, HttpSession session) { // 1.校驗(yàn)手機(jī)號(hào) if(RegexUtils.isPhoneInvalid(phone)){ // 說明:RegexUtils使我們封裝的一個(gè)類 isCodeInvalid是里面的靜態(tài)方法,在這個(gè)靜態(tài)方法里面又調(diào)用了另外一個(gè)靜態(tài)方法得以實(shí)現(xiàn) // 2.如果不符合,返回錯(cuò)誤信息 return Result.fail("手機(jī)號(hào)格式錯(cuò)誤"); } // 3.符合,生成驗(yàn)證碼 6代表生成的驗(yàn)證碼的長(zhǎng)度 RandomUtil使用這個(gè)工具類生成 String code = RandomUtil.randomNumbers(6); // 4.保存驗(yàn)證碼到session key必須是一個(gè)字符串,value是一個(gè)對(duì)象 session.setAttribute("code",code); // 5.發(fā)送驗(yàn)證碼 // 實(shí)現(xiàn)起來比較麻煩 我們使用日志假裝發(fā)送 log.debug("發(fā)送短信驗(yàn)證碼成功,驗(yàn)證碼:"+code); return Result.ok(); } }
三、登錄功能
3.1 短信驗(yàn)證的請(qǐng)求方式及路徑
/** * 登錄功能 * @param loginForm 登錄參數(shù),包含手機(jī)號(hào)、驗(yàn)證碼;或者手機(jī)號(hào)、密碼 */ @PostMapping("/login") public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){ // TODO 實(shí)現(xiàn)登錄功能 return userService.login(loginForm,session); }
3.2 業(yè)務(wù)層代碼實(shí)現(xiàn)用戶登錄
流程圖:
代碼:
/** * 實(shí)現(xiàn)用戶登錄 * @param loginForm 登錄的參數(shù) * @param session * @return */ @Override public Result login(LoginFormDTO loginForm, HttpSession session) { // 1.校驗(yàn)手機(jī)號(hào) if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){ // 說明:RegexUtils使我們封裝的一個(gè)類 isCodeInvalid是里面的靜態(tài)方法,在這個(gè)靜態(tài)方法里面又調(diào)用了另外一個(gè)靜態(tài)方法得以實(shí)現(xiàn) // 1.2.如果不符合,返回錯(cuò)誤信息 return Result.fail("手機(jī)號(hào)格式錯(cuò)誤"); } // 2.校驗(yàn)驗(yàn)證碼 // 2.1 得到code 這個(gè)值是真實(shí)的code Object cacheCode = session.getAttribute("code"); // 2.2 獲取用戶輸入的code String code = loginForm.getCode(); if(cacheCode ==null || !cacheCode.toString().equals(code)){ // 3.不一致,報(bào)錯(cuò) return Result.fail("驗(yàn)證碼錯(cuò)誤"); } // 4.一致,根據(jù)手機(jī)號(hào)查詢用戶 .one()代表查詢一個(gè) list()代表著查詢多個(gè) User user =query().eq("phone",loginForm.getPhone()).one(); // 5.判斷用戶是否存在 if(user ==null){ // 6.不存在,創(chuàng)建新用戶并保存 user = createUserWithPhone(loginForm.getPhone()); } // 7.保存用戶信息到session中 session.setAttribute("user",user); return Result.ok(); } private User createUserWithPhone(String phone) { // 1.創(chuàng)建用戶 User user = new User(); user.setPhone(phone); // USER_NICK_NAME_PREFIX其實(shí)就是 "user_",這樣寫更有逼格 user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10)); // 保存用戶 save(user); return user; }
3.3 攔截器——登錄驗(yàn)證功能
// HandlerInterceptor 這是一個(gè)攔截器 public class LoginInterceptor implements HandlerInterceptor { // 前置攔截 在進(jìn)入controller之前我們進(jìn)行登錄校驗(yàn) @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1.獲取session HttpSession session =request.getSession(); // 2.獲取session中的用戶 Object user = session.getAttribute("user"); // 3.判斷用戶是否存在 if(user == null){ // 4.不存在,攔截 response.setStatus(401); //返回401狀態(tài)碼 return false; } // 5.存在,保存用戶信息到ThreadLocal 保存在當(dāng)前線程里面的 UserHolder.saveUser((User)user); // 6.放行 return true; } // 在controller執(zhí)行之后攔截 這個(gè)我們?cè)谶@里不需要 // @Override // public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); // } // 渲染之后,返回給用戶之前 用戶業(yè)務(wù)執(zhí)行完畢我們要銷毀維護(hù)信息,避免泄露 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 移除用戶 UserHolder.removeUser(); } }
public class UserHolder { private static final ThreadLocal<User> tl = new ThreadLocal<>(); public static void saveUser(User user){ tl.set(user); } public static User getUser(){ return tl.get(); } public static void removeUser(){ tl.remove(); } }
@Configuration public class MvcConfig implements WebMvcConfigurer { // 攔截器的注冊(cè)器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .excludePathPatterns( "/user/code", "/user/login", "/shop/**", "/blog/hot", "/shop-type/**", "upload/**", "voucher/**" ); } }
@GetMapping("/me") public Result me(){ // TODO 獲取當(dāng)前登錄的用戶并返回 // 直接取就可以了 User user= UserHolder.getUser(); return Result.ok(user); }
三、隱藏用戶敏感信息
如下圖所示,服務(wù)器返回的信息有點(diǎn)多,我們?yōu)榱吮Wo(hù)用戶的信息,我們需要隱藏部分的內(nèi)容
所以一開始我們存入session的信息就不應(yīng)該是完整的信息,這樣才能降低服務(wù)器的壓力
UserServiceImpl中的login方法
// 7.保存用戶信息到session中 \ // BeanUtil.copyProperties(user, UserDTO.class)) 會(huì)自動(dòng)的將user中的屬性拷貝到UserDTO當(dāng)中而且也創(chuàng)建出一個(gè)UserDTO對(duì)象 session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
取的時(shí)候我們也應(yīng)該做出變化
LoginInterceptor類
// 5.存在,保存用戶信息到ThreadLocal 保存在當(dāng)前線程里面的 UserHolder.saveUser((UserDTO)user);
此時(shí)我們?cè)俚卿洸樵冃畔ⅲ瓦€剩下三個(gè)字段了
四、session共享問題
多臺(tái)Tomcat并不共享session存儲(chǔ)空間,當(dāng)請(qǐng)求切換到不同的Tomcat服務(wù)導(dǎo)致數(shù)據(jù)丟失的問題
所以這個(gè)方案就被pass了
session的替代方案應(yīng)該滿足:
數(shù)據(jù)共享內(nèi)存存儲(chǔ)key、value結(jié)構(gòu)
所以我們選擇Redis
任何一臺(tái)Tomcat都能訪問到Redis,這樣就能實(shí)現(xiàn)數(shù)據(jù)共享
總結(jié)
到此這篇關(guān)于如何基于Session實(shí)現(xiàn)短信登錄功能的文章就介紹到這了,更多相關(guān)Session短信登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis中事務(wù)機(jī)制及樂觀鎖的實(shí)現(xiàn)
這篇文章主要介紹了redis中事務(wù)機(jī)制及樂觀鎖的相關(guān)內(nèi)容,通過事務(wù)的執(zhí)行分析Redis樂觀鎖,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10Redis權(quán)限和訪問控制的實(shí)現(xiàn)示例
Redis提供了一些機(jī)制來保護(hù)敏感數(shù)據(jù)和限制對(duì)Redis服務(wù)器的訪問,本文主要介紹了Redis權(quán)限和訪問控制的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Redis實(shí)戰(zhàn)之商城購(gòu)物車功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Redis實(shí)戰(zhàn)之商城購(gòu)物車功能的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02基于Redis延遲隊(duì)列的實(shí)現(xiàn)代碼
在生活中很多時(shí)候都會(huì)用到延遲隊(duì)列,本文基于Redis延遲隊(duì)列的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Redis數(shù)據(jù)結(jié)構(gòu)之intset整數(shù)集合使用學(xué)習(xí)
這篇文章主要為大家介紹了Redis數(shù)據(jù)結(jié)構(gòu)之整數(shù)集合使用學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07redis配置standAlone版的jedisPool示例
這篇文章主要為大家介紹了redis配置standAlone版的jedisPool示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Redis中的3種特殊數(shù)據(jù)結(jié)構(gòu)詳解
在本文中,我們對(duì)三種特殊的數(shù)據(jù)類型進(jìn)行了介紹,它們分別是geospatial(地理空間數(shù)據(jù)類型)、HyperLogLogs和Bitmaps(位圖),這些數(shù)據(jù)類型在不同的領(lǐng)域和應(yīng)用中發(fā)揮著重要作用,并且具有各自獨(dú)特的特性和用途,對(duì)Redis特殊數(shù)據(jù)結(jié)構(gòu)相關(guān)知識(shí)感興趣的朋友一起看看吧2024-02-02