一、用戶登錄接口
請求參數:
用loginDTO來封裝請求參數,要加上@RequestBody注解
響應參數:
由于data里內容較多,考慮將其封裝到一個LoginUser的實體中,用戶登陸后,需要生成jwtToken并返回給前端。
登錄功能的service層具體實現步驟:
1.根據傳進來的account或email到數據庫里查,查出該用戶的信息。若兩者都不存在,則報錯。
2.校驗用戶的是否存在及密碼是否正確
3.生成jwt令牌。
4.修改最后登陸時間lastLoginAt
代碼如下:
@Overridepublic Result<LoginVO> login(LoginDTO loginDTO) {User loginUser = null;//1.if(loginDTO.getAccount() == null && loginDTO.getEmail() == null){return ResultUtil.error("請提供用戶名或郵箱!");}if(loginDTO.getAccount() == null && loginDTO.getEmail() != null){loginUser = userMapper.getUserByEmail(loginDTO.getEmail());}if(loginDTO.getEmail() == null && loginDTO.getAccount() != null){loginUser = userMapper.getUserByAccount(loginDTO.getAccount());}//2.if(loginUser == null){return ResultUtil.error("用戶不存在!請注冊!");}if( !loginDTO.getPassword().equals(loginUser.getPassword())){return ResultUtil.error("密碼錯誤!");}//3.String token = jwtUtil.generateToken(loginUser.getUserId());loginUser.setLastLoginAt(LocalDateTime.now());LoginVO loginVO = new LoginVO();BeanUtils.copyProperties(loginUser, loginVO);return Result.success(loginVO , token);}
二、設計jwt工具類
一個jwt工具類應該包含以下幾個功能:
1.生成jwt令牌
流程如下:
其中,claims是載荷,即往jwt令牌里存儲的信息,后續可以通過jwt令牌獲取這些信息。
使用jwts的builder()鏈式生成token,需要指定載荷、簽發時間、過期時間、簽名算法及密鑰secret等信息。
2.從前端傳來的jwt令牌里讀取信息
public Long getUserIdFromToken(String token) {try{Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();Long userId = (Long) claims.get("userId");return userId;}catch(Exception e){return null;}}
一個天坑:
此處jwt再解析時,claims.get("userID")一定要先轉換成字符串,再轉換成Long,若直接使用(long),由于JWT的聲明(claims)基于JSON標準,JSON不區分整數的具體類型(如Integer
或Long
)。當數值較小時,某些JWT實現(如JJWT)可能將其解析為Integer
,而較大的數值解析為Long
,直接強制類型轉換會導致異常。
因此:使用Long.valueOf()來間接轉換。
public Long getUserIdFromToken(String token) {try {Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();return Long.valueOf(claims.get("userId").toString());} catch (Exception e) {return null;}}
3.校驗jwt令牌是否有效
//方法3. 檢驗令牌是否有效public boolean validateToken(String token) {try {Jwts.parser().setSigningKey(secret).parseClaimsJws(token);return true;} catch (Exception e) {return false;}}
4.刷新jwt令牌
public String refreshToken(Long userId) {return generateToken(userId);}
三、獲取當前用戶信息
請求:無,只需攜帶jwt令牌
響應如下:跟LoginUserVo是同樣的
四、如何從請求中獲取jwt令牌?
在第三個需求中,需要先讀取jwt令牌,再從jwt令牌中讀取userID信息。因此,需要從請求中獲取jwt令牌。
這個需求的實現有幾種方案:
1.使用threadlocal來存儲和傳遞
2.使用request.setattribute
3.使用@RequestScope
此處選擇第三種。
實現步驟:
1.
定義?@RequestScope
?bean,需要加上@Component @RequestScope @Data 這幾個注解。
這個類的作用就是每遇到一個個http請求,就會創建它,我們可以將一些數據存進去,以便后續使用。
創建時機??:每個 HTTP 請求開始時,Spring 會創建一個新的?RequestContext
?實例。
??銷毀時機??:請求處理結束后,Spring 自動銷毀該實例
@Data
@RequestScope
@Component
public class RequestContext {public Long userId;public String token;public Boolean isLogin;
}
RequestContext中,isLogin的作用:存儲當前用戶有無登錄,若未登錄,設置成false,只允許其訪問登錄和注冊的服務。
2.在攔截器中注入并設置數據
3.將攔截器注冊到WebConfig中
4. 在 Controller 或 Service 中使用數據??
在需要訪問請求級別數據的組件中,直接注入?RequestContext
?Bean。
五、攔截器怎么寫
需求:每當遇到一個請求,就將他攔截下來,并從中讀取jwt 令牌。
具體步驟:
1.定義一個類,它必須實現??HandlerInterceptor ,
2.重寫preHandle這個方法,里面為 是否放行的邏輯
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 對于每個請求進行攔截,獲取請求頭中的 tokenString token = request.getHeader("Authorization");// 然后對 token 進行處理,并將 token 攜帶的信息存儲到,在請求周期中全局存在的 requestScopeData 中//0.token為nullif(token == null){requestContext.setToken(null);requestContext.setUserId(null);requestContext.setIsLogin(false);return true; //不管有沒有登錄,或者jwt令牌合法與否,都放行,后續對這些isLogin為false的請求只開放登陸的權限}token = token.replace("Bearer", "");// 若令牌不合法,requestContext的登錄狀態設置成falseif( ! jwtUtil.validateToken(token)){requestContext.setIsLogin(false);}else{requestContext.setIsLogin(true);requestContext.setToken(token);requestContext.setUserId(jwtUtil.getUserIdFromToken(token));}return true;}
3.配置攔截器
//配置攔截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor).addPathPatterns("/**").excludePathPatterns("/login", "/error");}
}
六、今日踩坑小結
1.請求體若使用實體類封裝 , 必須加上@RequestBody , 否則后端接受不到前端傳來的數據
2.當前端傳來的參數和方法參數不一致時使用@RequestParam注解,當然,前端傳來多個參數時必須使用
@GetMapping("/user")
public String getUser(@RequestParam("id") Long userId) {// 通過 /user?id=123 訪問時,userId=123return "user";
}
3.@PathVariable
4.@Param注解:mapper層定義的方法,當接受多個參數時,必須使用
// Mapper接口
public interface UserMapper {List<User> findUsersByConditions(@Param("name") String name,@Param("status") Integer status);
}