SpringBoot基于redis實(shí)現(xiàn)登錄校驗(yàn)的示例
實(shí)現(xiàn)流程
發(fā)送短信驗(yàn)證碼,并將驗(yàn)證碼保存在redis。
根據(jù)用戶登錄數(shù)據(jù)完成登錄。
- 如果是新用戶還要?jiǎng)?chuàng)建用戶。
- 如果查詢到則轉(zhuǎn)化為DTO保存。
將用戶數(shù)據(jù)被保存到redis,并將token保存到redis,同時(shí)返回給前端。
創(chuàng)建登錄校驗(yàn)攔截器。
注冊登錄校驗(yàn)攔截器。
發(fā)送驗(yàn)證碼接口
請求參數(shù):手機(jī)號phone
無結(jié)果對象
service層實(shí)現(xiàn)
@Override
public Result sendCode(String phone) {
//1.校驗(yàn)手機(jī)號
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手機(jī)號格式錯(cuò)誤!");
}
//2.生成驗(yàn)證碼
String code = RandomUtil.randomNumbers(6);
//3.將驗(yàn)證碼保存在redis
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
//4.模擬發(fā)送驗(yàn)證碼
log.debug("發(fā)送短信驗(yàn)證碼成功,驗(yàn)證碼:{}", code);
return Result.ok();
}
接口介紹
- 根據(jù)正則表達(dá)式檢驗(yàn)手機(jī)號格式。
- 通過hutool生成6位隨機(jī)數(shù)作為驗(yàn)證碼。
- 將驗(yàn)證碼保存在redis中。
- 將驗(yàn)證碼發(fā)送給客戶端(此處應(yīng)使用相關(guān)云服務(wù)完成,筆者使用日志只為記錄數(shù)據(jù))。
登錄接口
請求參數(shù):已經(jīng)封裝好的LoginFormDTO對象
結(jié)果對象:token
service層實(shí)現(xiàn)
@Override
public Result login(LoginFormDTO loginForm) {
//1.校驗(yàn)手機(jī)號格式
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手機(jī)號格式錯(cuò)誤!");
}
//2.校驗(yàn)驗(yàn)證碼
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.equals(code)) {
return Result.fail("驗(yàn)證碼錯(cuò)誤!");
}
//3.根據(jù)手機(jī)號查詢用戶
User user = query().eq("phone", phone).one();
//4.判斷用戶是否存在
if (user == null) {
//5.沒有用戶則創(chuàng)建新用戶
user = createUserWithPhone(phone);
}
//6.保存用戶信息到redis,(以哈希儲(chǔ)存)
//1.通過UUID拼裝tokenKey
String token = UUID.randomUUID().toString(true);
String tokenKey = LOGIN_USER_KEY + token;
//2.將UserDTO對象轉(zhuǎn)為HashMap存儲(chǔ), 由于使用的是stringRedisTemplate,要保證所有的值都是字符串類型,要把lang轉(zhuǎn)換為string
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions
.create()//創(chuàng)建默認(rèn)數(shù)據(jù)拷貝選項(xiàng)
.setIgnoreNullValue(true) //忽略null值
/*字段值修改器,接收實(shí)現(xiàn)修改的方法*/
.setFieldValueEditor(
(fieldName, fieldValue) -> fieldValue.toString()
)
);
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
//3.設(shè)置token的有效期
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.SECONDS);
//7.返回token
return Result.ok(tokenKey);
}
接口介紹
- 校驗(yàn)手機(jī)號格式。
- 校驗(yàn)驗(yàn)證碼。
- 根據(jù)手機(jī)號查詢用戶信息(使用MybatisPlus)。
- 通過UUID生成隨機(jī)token,所為key,將查詢后得到的UserDTO(將User轉(zhuǎn)換為UserDTO,避免暴漏敏感數(shù)據(jù)),封裝為hash(使用hutool相關(guān)API),存入redis,并設(shè)置有效期。
- 返回token。
手動(dòng)創(chuàng)建攔截器
在實(shí)現(xiàn)了HandlerInterceptor的類中重寫preHandle和afterCompletion來實(shí)現(xiàn)對每一次請求的處理。
public class LoginInInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public LoginInInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//獲取請求頭中的token
String token = request.getHeader("authorization");
if (token == null) {
response.setStatus(401);
return false;
}
//根據(jù)token從redis中獲取用戶信息
String key = LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
if (userMap.isEmpty()) {
response.setStatus(401);
return false;
}
//將map轉(zhuǎn)換為UserDTO對象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//保存用戶信息到ThreadLocal
UserHolder.saveUser(userDTO);
//刷新token有效期
stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.SECONDS);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
UserHolder.removeUser();
}
}
用前注意:
該自定義攔截器未交給Spring容器管理,不能使用@Autowired或者@Resource自動(dòng)注入依賴,此處使用構(gòu)造器注入stringRedisTemplate。
執(zhí)行邏輯:
preHandle執(zhí)行攔截校驗(yàn)
- getHeader獲取請求頭中的token,并進(jìn)行非空判斷。
- 根據(jù)token從redis中獲取userMap,再次使用hutool包的API將map集合轉(zhuǎn)換為實(shí)體類。
- 使用工具類將userDTO保存到ThreadLocal,實(shí)現(xiàn)跨層級數(shù)據(jù)共享。
- 刷新token有效期,提升用戶體驗(yàn)。
afterCompletion執(zhí)行清理ThreadLocal
請求完成后清理ThreadLocal中的數(shù)據(jù),防止內(nèi)存泄漏和數(shù)據(jù)污染。
注冊攔截器
在含有@Configuration 且實(shí)現(xiàn)了WebMvcConfigurer 的配置類中添加自定義攔截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInInterceptor(stringRedisTemplate))
.excludePathPatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**"
);
}
}
執(zhí)行邏輯:
- 使用@Resource注解注入stringRedisTemplate
- 重寫addInterceptors方法
- 調(diào)用registry的addInterceptor方法添加攔截器(形參中new出LoginInInterceptor,并將正確注入的stringRedisTemplate傳遞給LoginInInterceptor,完成自定義攔截器中的依賴注入)。
- 調(diào)用excludePathPatterns指明不攔截的請求
攔截器的優(yōu)化
由于將校驗(yàn)登錄狀態(tài)和刷新token有效期合并在了一個(gè)攔截器中,所以在用戶訪問一些不攔截登錄的接口時(shí)無法刷新token有效期,為此,我們將它拆分為兩個(gè)攔截器
刷新攔截器:
只為刷新token有效期
- 如果無token直接放行讓用戶進(jìn)行登錄
- 如果有token則將用戶保存在ThreadLoacl(便于攔截登錄)并刷新token有效期
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 {
//獲取請求頭中的token
String token = request.getHeader("authorization");
if (token == null) {
return true;
}
//根據(jù)token從redis中獲取用戶信息
String key = token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
if (userMap.isEmpty()) {
return true;
}
//將map轉(zhuǎn)換為UserDTO對象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//保存用戶信息到ThreadLocal
UserHolder.saveUser(userDTO);
//刷新token有效期
stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.SECONDS);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
UserHolder.removeUser();
}
}
登錄攔截器:
只為攔截未登錄用戶
public class LoginInInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判斷是否需要攔截(ThreadLocal中是否有用戶)
if (UserHolder.getUser() == null) {
//無用戶,進(jìn)行攔截
response.setStatus(401);
return false;
}
//有用戶,放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
UserHolder.removeUser();
}
}
配置攔截順序
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInInterceptor())
.excludePathPatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**"
).order(1);
registry.addInterceptor(new ReFreshTokenInterceptor(stringRedisTemplate)).order(0);
}
}
order值低的優(yōu)先進(jìn)行攔截
到此這篇關(guān)于SpringBoot基于redis實(shí)現(xiàn)登錄校驗(yàn)的示例的文章就介紹到這了,更多相關(guān)SpringBoot redis登錄校驗(yàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)數(shù)字轉(zhuǎn)大寫的方法
這篇文章主要介紹了 java實(shí)現(xiàn)數(shù)字轉(zhuǎn)大寫的方法的相關(guān)資料,希望通過本文能幫助到大家,讓大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10
Java如何將String轉(zhuǎn)換成json對象或json數(shù)組
這篇文章主要介紹了Java如何將String轉(zhuǎn)換成json對象或json數(shù)組,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Intellij?IDEA創(chuàng)建web項(xiàng)目的超詳細(xì)步驟記錄
如果剛開始接觸IDEA,或者之前使用的是eclipse/myEclipse的話,即使是創(chuàng)建一個(gè)JAVA WEB項(xiàng)目,估計(jì)也讓很多人費(fèi)了好幾個(gè)小時(shí),下面這篇文章主要給大家介紹了關(guān)于Intellij?IDEA創(chuàng)建web項(xiàng)目的超詳細(xì)步驟,需要的朋友可以參考下2022-08-08
ElasticSearch學(xué)習(xí)之Es集群Api操作示例
這篇文章主要為大家介紹了ElasticSearch學(xué)習(xí)之Es集群Api操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
tomcat報(bào)錯(cuò):Wrapper cannot find servlet class ...問題解決
這篇文章主要介紹了tomcat報(bào)錯(cuò):Wrapper cannot find servlet class ...問題解決的相關(guān)資料,需要的朋友可以參考下2016-11-11
注入jar包里的對象,用@autowired的實(shí)例
這篇文章主要介紹了注入jar包里的對象,用@autowired的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09

