網站首頁 編程語言 正文
1. 基于 session 實現短信登錄
1.1 短信登錄流程圖
1.2 實現發送短信驗證碼
前端請求說明:
? | 說明 |
---|---|
請求方式 | POST |
請求路徑 | /user/code |
請求參數 | phone(電話號碼) |
返回值 | 無 |
后端接口實現:
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 校驗手機號
if(RegexUtils.isPhoneInvalid(phone)){
// 2. 如果不符合,返回錯誤信息
return Result.fail("手機號格式錯誤!");
}
// 3. 符合,生成驗證碼(設置生成6位)
String code = RandomUtil.randomNumbers(6);
// 4. 保存驗證碼到 session
session.setAttribute("code", code);
// 5. 發送驗證碼(這里并未實現,通過日志記錄)
log.debug("發送短信驗證碼成功,驗證碼:{}", code);
// 返回 ok
return Result.ok();
}
}
1.3 實現短信驗證碼登錄、注冊
前端請求說明
? | 說明 |
---|---|
請求方式 | POST |
請求路徑 | /user/login |
請求參數 | phone(電話號碼);code(驗證碼) |
返回值 | 無 |
后端接口實現:
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 校驗手機號
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
// 不一致,返回錯誤信息
return Result.fail("手機號格式錯誤!");
}
// 2. 校驗驗證碼
String cacheCode = (String) session.getAttribute("code");
String code = loginForm.getCode();
if(cacheCode == null || !cacheCode.equals(cacheCode)){
// 不一致,返回錯誤信息
return Result.fail("驗證碼錯誤!");
}
// 4. 一致,根據手機號查詢用戶(這里使用的 mybatis-plus)
User user = query().eq("phone", phone).one();
// 5. 判斷用戶是否存在
if(user == null){
// 6. 不存在,創建新用戶并保存
user = createUserWithPhone(phone);
}
// 7. 保存用戶信息到 session 中(通過 BeanUtil.copyProperties 方法將 user 中的信息過濾到 UserDTO 上,即用來隱藏部分信息)
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
private User createUserWithPhone(String phone) {
// 1. 創建用戶
User user = new User();
user.setPhone(phone);
user.setNickName("user_" + RandomUtil.randomString(10));
// 2. 保存用戶(這里使用 mybatis-plus)
save(user);
return user;
}
}
1.4 實現登錄校驗攔截器
登錄校驗攔截器實現:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 獲取 session
HttpSession session = request.getSession();
// 2. 獲取 session 中的用戶
UserDTO user = (UserDTO) session.getAttribute("user");
// 3. 判斷用戶是否存在
if(user == null){
// 4. 不存在,攔截,返回 401 未授權
response.setStatus(401);
return false;
}
// 5. 存在,保存用戶信息到 ThreadLocal
UserHolder.saveUser(user);
// 6. 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用戶,避免內存泄露
UserHolder.removeUser();
}
}
UserHolder 類的實現: 該類中定義了一個靜態的 ThreadLocal
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}
public static UserDTO getUser(){
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
配置攔截器:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/login",
"/user/code"
);
}
}
前端請求說明:
? | 說明 |
---|---|
請求方式 | POST |
請求路徑 | /user/me |
請求參數 | 無 |
返回值 | 無 |
后端接口實現:
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result me() {
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
}
2. 集群的 session 共享問題
session 共享問題:
多臺 tomcat 并不共享 session 存儲空間,當請求切換到不同 tomcat 服務時會導致數據丟失的問題。
session 的替代方案應該滿足以下條件:
- 數據共享(不同的 tomcat 都能夠訪問 Redis 中的數據)
- 內存存儲(Redis 通過內存存儲)
- key、value 結構(Redis 是 key-value 結構)
3. 基于 Redis 實現共享 session 登錄
3.1 Redis 實現共享 session 登錄流程圖
3.2 實現發送短信驗證碼
前端請求說明:
? | 說明 |
---|---|
請求方式 | POST |
請求路徑 | /user/code |
請求參數 | phone(電話號碼) |
返回值 | 無 |
后端接口實現:
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 校驗手機號
if (RegexUtils.isPhoneInvalid(phone)) {
// 2. 如果不符合,返回錯誤信息
return Result.fail("手機號格式錯誤!");
}
// 3. 符合,生成驗證碼(設置生成6位)
String code = RandomUtil.randomNumbers(6);
// 4. 保存驗證碼到 Redis(以手機號為 key,設置有效期為 2min)
stringRedisTemplate.opsForValue().set("login:code:" + phone, code, 2, TimeUnit.MINUTES);
// 5. 發送驗證碼(這里并未實現,通過日志記錄)
log.debug("發送短信驗證碼成功,驗證碼:{}", code);
// 返回 ok
return Result.ok();
}
}
3.3 實現短信驗證碼登錄、注冊
前端請求說明:
? | 說明 |
---|---|
請求方式 | POST |
請求路徑 | /user/login |
請求參數 | phone(電話號碼);code(驗證碼) |
返回值 | 無 |
后端接口實現:
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 校驗手機號
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
// 不一致,返回錯誤信息
return Result.fail("手機號格式錯誤!");
}
// 2. 校驗驗證碼
String cacheCode = (String) session.getAttribute("code");
String code = loginForm.getCode();
if(cacheCode == null || !cacheCode.equals(cacheCode)){
// 不一致,返回錯誤信息
return Result.fail("驗證碼錯誤!");
}
// 4. 一致,根據手機號查詢用戶(這里使用的 mybatis-plus)
User user = query().eq("phone", phone).one();
// 5. 判斷用戶是否存在
if(user == null){
// 6. 不存在,創建新用戶并保存
user = createUserWithPhone(phone);
}
// 7. 保存用戶信息到 session 中(通過 BeanUtil.copyProperties 方法將 user 中的信息過濾到 UserDTO 上,即用來隱藏部分信息)
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
private User createUserWithPhone(String phone) {
// 1. 創建用戶
User user = new User();
user.setPhone(phone);
user.setNickName("user_" + RandomUtil.randomString(10));
// 2. 保存用戶(這里使用 mybatis-plus)
save(user);
return user;
}
}
3.4 實現登錄校驗攔截器
這里將原有的一個攔截器分成兩個攔截器,第一個攔截器對所有的請求進行攔截,每次攔截刷新 token 的有效期,并將能查詢到的用戶信息保存到 ThreadLocal 中。第二個攔截器則進行攔截功能,對需要登錄的路徑進行攔截。
刷新 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 {
// 1. 獲取請求頭中的 token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
return true;
}
// 2. 基于 token 獲取 redis 中的用戶
String tokenKey = "login:token:" + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
// 3. 判斷用戶是否存在
if (userMap.isEmpty()) {
return true;
}
// 5. 將查詢到的 Hash 數據轉為 UserDTO 對象
UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 6. 存在,保存用戶信息到 ThreadLocal
UserHolder.saveUser(user);
// 7. 刷新 token 有效期 30 min
stringRedisTemplate.expire(tokenKey, 30, TimeUnit.MINUTES);
// 8. 放行
return true;
}
}
登錄校驗攔截器實現:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 獲取 session
HttpSession session = request.getSession();
// 2. 獲取 session 中的用戶
UserDTO user = (UserDTO) session.getAttribute("user");
// 3. 判斷用戶是否存在
if(user == null){
// 4. 不存在,攔截,返回 401 未授權
response.setStatus(401);
return false;
}
// 5. 存在,保存用戶信息到 ThreadLocal
UserHolder.saveUser(user);
// 6. 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用戶,避免內存泄露
UserHolder.removeUser();
}
}
UserHolder 類的實現: 該類中定義了一個靜態的 ThreadLocal
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}
public static UserDTO getUser(){
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
配置攔截器:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
.addPathPatterns("/**").order(0);
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/login",
"/user/code"
).order(1);
}
}
前端請求說明:
? | 說明 |
---|---|
請求方式 | POST |
請求路徑 | /user/me |
請求參數 | 無 |
返回值 | 無 |
后端接口實現:
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result me() {
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
}
原文鏈接:https://blog.csdn.net/weixin_51367845/article/details/126293976
相關推薦
- 2021-11-02 Linux環境下生成openssl證書注意細節介紹_Linux
- 2022-07-23 asp.net6?blazor?文件上傳功能_實用技巧
- 2023-07-04 在SpringBoot中如何整合數據源?
- 2022-03-30 圖文詳解nginx日志切割的實現_nginx
- 2023-03-28 基于Python開發云主機類型管理腳本分享_python
- 2022-04-25 python?實現兩個字符串乘法小練習_python
- 2022-07-07 淺談Redis的異步機制_Redis
- 2023-07-28 nrm的安裝與配置及問題修復
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支