Spring Security+JWT實(shí)現(xiàn)登錄認(rèn)證的完整實(shí)踐指南
一、前言
在傳統(tǒng) Web 項(xiàng)目中,用戶登錄后通常使用 Session 保存登錄狀態(tài):
用戶登錄
↓
服務(wù)端創(chuàng)建Session
↓
瀏覽器保存Cookie
↓
后續(xù)請(qǐng)求攜帶Cookie
但是在微服務(wù)架構(gòu)中:
Gateway
↓
User-Service
Order-Service
Product-Service
Session 會(huì)面臨以下問(wèn)題:
- 服務(wù)擴(kuò)容 Session 不共享
- 微服務(wù)之間認(rèn)證困難
- 前后端分離支持較差
因此目前企業(yè)項(xiàng)目大多采用:
Spring Security
+
JWT
實(shí)現(xiàn)無(wú)狀態(tài)登錄認(rèn)證。
二、Spring Security 核心對(duì)象
Spring Security 最核心的是三個(gè)對(duì)象:
SecurityContextHolder
↓
SecurityContext
↓
Authentication
1、Authentication
Authentication 表示當(dāng)前登錄用戶。
Spring Security 所有認(rèn)證信息最終都會(huì)放入 Authentication。
例如:
Authentication authentication = SecurityContextHolder
.getContext()
.getAuthentication();
2、Principal
Authentication 中保存真正的用戶對(duì)象:
Object principal = authentication.getPrincipal();
這個(gè)對(duì)象可以是:
User LoginUser JwtUser GlobalJwtUser
Spring Security 并不關(guān)心具體類型。
只要實(shí)現(xiàn)認(rèn)證成功即可。
3、SecurityContextHolder
用于保存當(dāng)前請(qǐng)求認(rèn)證信息。
結(jié)構(gòu)如下:
SecurityContextHolder
↓
SecurityContext
↓
Authentication
↓
JwtUser
三、JWT 登錄認(rèn)證整體流程
完整認(rèn)證流程如下:
用戶登錄
↓
用戶名密碼校驗(yàn)
↓
生成JWT
↓
返回前端
================================
后續(xù)請(qǐng)求
Authorization: Bearer xxx
↓
JwtAuthenticationFilter
↓
解析JWT
↓
獲取JwtUser
↓
構(gòu)造Authentication
↓
放入SecurityContext
↓
Controller
Service
四、Bearer 是什么
很多人第一次接觸 JWT 都會(huì)看到:
Authorization: Bearer eyJhbGciOiJIUzI1Ni...
這里:
Bearer
表示認(rèn)證方案。
格式遵循 HTTP 標(biāo)準(zhǔn):
Authorization: <認(rèn)證方式> <認(rèn)證信息>
例如:
Authorization: Basic xxxxx Authorization: Bearer xxxxx
JWT 標(biāo)準(zhǔn)寫法就是:
Authorization: Bearer token
Spring Security 默認(rèn)也是按照這個(gè)格式解析。
五、自定義用戶對(duì)象
企業(yè)項(xiàng)目一般不會(huì)直接使用 User。
通常會(huì)定義自己的用戶對(duì)象:
@Data
public class JwtUser {
private Long userId;
private String username;
private String deptCode;
}
這個(gè)對(duì)象最終會(huì)存入 Spring Security 上下文。
六、登錄成功生成 JWT
登錄接口:
@PostMapping("/login")
public String login(LoginRequest request){
Authentication authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()));
JwtUser user =
(JwtUser) authentication.getPrincipal();
return jwtService.generateToken(user);
}
認(rèn)證成功:
用戶名密碼
↓
數(shù)據(jù)庫(kù)校驗(yàn)
↓
生成JWT
↓
返回前端
返回結(jié)果:
{
"token":"eyJhbGciOiJIUzI1Ni..."
}
七、JWT 過(guò)濾器解析 Token
企業(yè)項(xiàng)目核心代碼基本都在過(guò)濾器中。
例如:
public class JwtAuthenticationFilter
extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
String token =
request.getHeader("Authorization");
if(token != null){
JwtUser user =
parseToken(token);
JwtAuthentication authentication =
new JwtAuthentication(user);
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
}
filterChain.doFilter(request,response);
}
}
作用:
請(qǐng)求進(jìn)入 ↓ 解析JWT ↓ 獲得JwtUser ↓ 存入Spring Security上下文
八、自定義 Authentication
Spring Security 實(shí)際保存的是 Authentication。
因此一般會(huì)定義:
public class JwtAuthentication
extends AbstractAuthenticationToken {
private final JwtUser jwtUser;
public JwtAuthentication(JwtUser jwtUser) {
super(null);
this.jwtUser = jwtUser;
setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return jwtUser;
}
@Override
public Object getCredentials() {
return null;
}
}
重點(diǎn):
getPrincipal()
返回:
JwtUser
九、為什么 setAuthentication 如此重要
認(rèn)證成功后:
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
實(shí)際上完成了:
SecurityContextHolder
↓
Authentication
↓
JwtUser
的綁定。
這是整個(gè) Spring Security 認(rèn)證體系最關(guān)鍵的一步。
如果沒(méi)有這一步:
SecurityContextHolder
.getContext()
.getAuthentication();
獲取到的就是空。
十、Controller 如何自動(dòng)獲得當(dāng)前用戶
很多項(xiàng)目中會(huì)看到:
@PostMapping("/save")
public void save(
@CurrentJwtUser JwtUser user) {
}
為什么能夠自動(dòng)注入?
1、自定義注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
public @interface CurrentJwtUser {
}
本質(zhì)上:
@CurrentJwtUser
就是:
@AuthenticationPrincipal
的二次封裝。
2、Spring Security 自動(dòng)解析
Spring Security 內(nèi)部提供:
AuthenticationPrincipalArgumentResolver
專門處理:
@AuthenticationPrincipal
參數(shù)。
執(zhí)行流程:
Controller請(qǐng)求
↓
發(fā)現(xiàn)參數(shù)
@CurrentJwtUser
↓
讀取Authentication
↓
調(diào)用
authentication.getPrincipal()
↓
獲得JwtUser
↓
自動(dòng)注入
因此:
@CurrentJwtUser JwtUser user
實(shí)際上等價(jià)于:
JwtUser user =
(JwtUser)
SecurityContextHolder
.getContext()
.getAuthentication()
.getPrincipal();
十一、業(yè)務(wù)代碼獲取當(dāng)前用戶
很多 Service 層也需要當(dāng)前用戶。
通常封裝工具類:
public class SecurityUtil {
public static JwtUser currentUser(){
Authentication authentication =
SecurityContextHolder
.getContext()
.getAuthentication();
if(authentication == null){
return null;
}
return (JwtUser)
authentication.getPrincipal();
}
}
使用:
JwtUser user =
SecurityUtil.currentUser();
Long userId =
user.getUserId();
十二、完整認(rèn)證鏈路分析
整個(gè)流程串起來(lái)如下:
用戶請(qǐng)求
Authorization: Bearer token
↓
JwtAuthenticationFilter
↓
解析JWT
↓
JwtUser
↓
JwtAuthentication
↓
SecurityContextHolder
↓
Controller
↓
@AuthenticationPrincipal
↓
Authentication.getPrincipal()
↓
JwtUser自動(dòng)注入
十三、為什么 Controller 和 Service 獲取的是同一個(gè)用戶
Controller:
@CurrentJwtUser JwtUser user
Service:
SecurityUtil.currentUser()
雖然寫法不同。
但最終訪問(wèn)的都是:
SecurityContextHolder
↓
SecurityContext
↓
Authentication
↓
JwtUser
因此拿到的是同一個(gè)對(duì)象。
十四、企業(yè)項(xiàng)目最佳實(shí)踐
推薦架構(gòu):
Spring Security
+
JWT
+
Gateway
+
Redis
流程:
登錄 ↓ 認(rèn)證中心 ↓ JWT ↓ Gateway統(tǒng)一校驗(yàn) ↓ 解析用戶信息 ↓ 寫入上下文 ↓ 業(yè)務(wù)服務(wù)直接獲取用戶
優(yōu)點(diǎn):
- 無(wú)狀態(tài)認(rèn)證
- 支持微服務(wù)
- 支持水平擴(kuò)容
- 支持統(tǒng)一權(quán)限控制
- 支持單點(diǎn)登錄
十五、總結(jié)
Spring Security + JWT 的核心只有一句話:
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
認(rèn)證成功后:
JwtUser
↓
JwtAuthentication
↓
SecurityContextHolder
隨后:
@AuthenticationPrincipal
或者:
SecurityContextHolder
都能獲取當(dāng)前登錄用戶。
整個(gè)認(rèn)證鏈路可以概括為:
JWT
↓
Filter
↓
JwtUser
↓
Authentication
↓
SecurityContextHolder
↓
Controller/Service
理解了這條鏈路,就真正理解了 Spring Security 的認(rèn)證機(jī)制。
以上就是Spring Security+JWT實(shí)現(xiàn)登錄認(rèn)證的完整實(shí)踐指南的詳細(xì)內(nèi)容,更多關(guān)于Spring Security JWT登錄認(rèn)證的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA報(bào)錯(cuò)Error?running‘Application‘:Command?line?is?too?lo
這篇文章主要介紹了IDEA報(bào)錯(cuò)Error?running?‘Application‘:Command?line?is?too?long的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
解決mapper.xml中resultType映射類型的問(wèn)題
這篇文章主要介紹了解決mapper.xml中resultType映射類型的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
Java的Struts框架中登陸功能的實(shí)現(xiàn)和表單處理器的使用
這篇文章主要介紹了Java的Struts框架中登陸功能的實(shí)現(xiàn)和表單處理器的使用,Struts框架是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12
Java實(shí)現(xiàn)的校驗(yàn)銀行卡功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)的校驗(yàn)銀行卡功能,結(jié)合完整實(shí)例形式分析了java針對(duì)銀行卡類型、歸屬地等信息的判斷、讀取相關(guān)操作技巧,需要的朋友可以參考下2018-06-06
一分鐘掌握J(rèn)ava?Quartz定時(shí)任務(wù)
這篇文章主要為大家介紹了Java?Quartz定時(shí)任務(wù)一分鐘掌握教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
Java commons io包實(shí)現(xiàn)多線程同步圖片下載入門教程
這篇文章主要介紹了Java commons io包實(shí)現(xiàn)多線程同步圖片下載入門,commons io: 是針對(duì)開發(fā)IO流功能的工具類庫(kù),其中包含了許多可調(diào)用的函數(shù),感興趣的朋友跟隨小編一起看看吧2021-04-04
JavaWeb的監(jiān)聽(tīng)器和過(guò)濾器你了解嗎
這篇文章主要為大家詳細(xì)介紹了JavaWeb的監(jiān)聽(tīng)器和過(guò)濾器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02
SpringBoot應(yīng)用War包形式部署到外部Tomcat的方法
這篇文章主要介紹了SpringBoot應(yīng)用War包形式部署到外部Tomcat的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Java調(diào)用DOS實(shí)現(xiàn)定時(shí)關(guān)機(jī)的實(shí)例
Java調(diào)用DOS實(shí)現(xiàn)定時(shí)關(guān)機(jī)的實(shí)例,需要的朋友可以參考一下2013-04-04

