github 建網(wǎng)站行業(yè)網(wǎng)站有哪些平臺(tái)
Redis代替session 實(shí)現(xiàn)登錄流程
????????如果使用String,他的value,用多占用一點(diǎn)空間,如果使用哈希,則他的value中只會(huì)存儲(chǔ)他數(shù)據(jù)本身,如果不是特別在意內(nèi)存,其實(shí)使用String就可以
設(shè)計(jì)key的具體細(xì)節(jié)
在設(shè)計(jì)這個(gè)key的時(shí)候,我們之前講過(guò)需要滿足兩點(diǎn)
- key要具有唯一性
- key要方便攜帶
????????如果我們采用phone:手機(jī)號(hào)這個(gè)的數(shù)據(jù)來(lái)存儲(chǔ)當(dāng)然是可以的,但是如果把這樣的敏感數(shù)據(jù)存儲(chǔ)到redis中并且從頁(yè)面中帶過(guò)來(lái)畢竟不太合適,所以我們?cè)?span style="color:#fe2c24;">后臺(tái)生成一個(gè)隨機(jī)串token,然后讓前端帶來(lái)這個(gè)token就能完成我們的整體邏輯了
整體訪問(wèn)流程
????????當(dāng)注冊(cè)完成后,用戶去登錄會(huì)去校驗(yàn)用戶提交的手機(jī)號(hào)和驗(yàn)證碼,是否一致,如果一致,則根據(jù)手機(jī)號(hào)查詢(xún)用戶信息,不存在則新建,最后將用戶數(shù)據(jù)保存到redis,并且生成token作為redis的key,當(dāng)我們校驗(yàn)用戶是否登錄時(shí),會(huì)去攜帶著token進(jìn)行訪問(wèn),從redis中取出token對(duì)應(yīng)的value,判斷是否存在這個(gè)數(shù)據(jù),如果沒(méi)有則攔截,如果存在則將其保存到threadLocal中,并且放行。
基于Redis實(shí)現(xiàn)短信登錄
UserServiceImpl代碼
// public static final String LOGIN_CODE_KEY = "login:code:";
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校驗(yàn)手機(jī)號(hào)String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯(cuò)誤信息return Result.fail("手機(jī)號(hào)格式錯(cuò)誤!");}// 3.從redis獲取驗(yàn)證碼并校驗(yàn)String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 不一致,報(bào)錯(cuò)return Result.fail("驗(yàn)證碼錯(cuò)誤");}// 4.一致,根據(jù)手機(jī)號(hào)查詢(xún)用戶 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 5.判斷用戶是否存在if (user == null) {// 6.不存在,創(chuàng)建新用戶并保存user = createUserWithPhone(phone);}// 7.保存用戶信息到 redis中// 7.1.隨機(jī)生成token,作為登錄令牌String token = UUID.randomUUID().toString(true);// 7.2.將User對(duì)象轉(zhuǎn)為HashMap存儲(chǔ)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()));// 7.3.存儲(chǔ)String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.設(shè)置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);
}
解決狀態(tài)登錄刷新問(wèn)題
????????在這個(gè)方案中,他確實(shí)可以使用對(duì)應(yīng)路徑的攔截,同時(shí)刷新登錄token令牌的存活時(shí)間,但是現(xiàn)在這個(gè)攔截器他只是攔截需要被攔截的路徑,假設(shè)當(dāng)前用戶訪問(wèn)了一些不需要攔截的路徑,那么這個(gè)攔截器就不會(huì)生效,所以此時(shí)令牌刷新的動(dòng)作實(shí)際上就不會(huì)執(zhí)行,所以這個(gè)方案他是存在問(wèn)題的
????????既然之前的攔截器無(wú)法對(duì)不需要攔截的路徑生效,那么我們可以添加一個(gè)攔截器,在第一個(gè)攔截器中攔截所有的路徑,把第二個(gè)攔截器做的事情放入到第一個(gè)攔截器中,同時(shí)刷新令牌,因?yàn)榈谝粋€(gè)攔截器有了threadLocal的數(shù)據(jù),所以此時(shí)第二個(gè)攔截器只需要判斷攔截器中的user對(duì)象是否存在即可,完成整體刷新功能。
RefreshTokenInterceptor
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 {// 1.獲取請(qǐng)求頭中的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.將查詢(xún)到的hash數(shù)據(jù)轉(zhuǎn)為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();}
}
LoginInterceptor
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判斷是否需要攔截(ThreadLocal中是否有用戶)if (UserHolder.getUser() == null) {// 沒(méi)有,需要攔截,設(shè)置狀態(tài)碼response.setStatus(401);// 攔截return false;}// 有用戶,則放行return true;}
}