如何使用JWT的SpringSecurity實(shí)現(xiàn)前后端分離
1. SpringSecurity完成前后端完全分離
分析:
前后端分離:響應(yīng)的數(shù)據(jù)必須為JSON數(shù)據(jù),之前響應(yīng)的是網(wǎng)頁(yè)
需要修改的代碼有:
登錄成功需要返回json數(shù)據(jù)登錄失敗需要返回json數(shù)據(jù)權(quán)限不足時(shí)返回json數(shù)據(jù)未登錄訪問(wèn)資源返回json數(shù)據(jù)
1.1 登錄成功需要返回json數(shù)據(jù)
第一種方案:基于redis 實(shí)現(xiàn)session共享

該方案的缺點(diǎn):
redis壓力太大項(xiàng)目依賴于第三方組件
第二種方案:基于jwt【采用】
jwt幫你生成唯一標(biāo)志,而且校驗(yàn)唯一標(biāo)志。信息存放在jwt中,同時(shí)可以從jwt中獲取

1.2 JWT的概述
1.2.1 什么是JWT
Json web token (JWT),是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)((RFC7519).該token被設(shè)計(jì)為緊湊且安全的,特別適用于分布式站點(diǎn)的單點(diǎn)登錄(SSO)場(chǎng)景。JWT的聲明一般被用來(lái)在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認(rèn)證,也可被加密。
官網(wǎng): https://jwt.io/introduction/
JWT就是token的一種具體實(shí)現(xiàn)方式,本質(zhì)就是一個(gè)字符串,它將用戶信息保存到一個(gè)json字符串中,然后進(jìn)行編碼后得到一個(gè)
JWT token,并且這個(gè)JWT token帶有簽名信息,接收后可以校驗(yàn)是否被篡改。所以可以用于在各方之間安全地將信息作為JSON對(duì)象傳輸。
1.2.2 前后端完全分離認(rèn)證問(wèn)題
互聯(lián)網(wǎng)服務(wù)離不開用戶認(rèn)證。一般流程是下面這樣。
1、用戶向服務(wù)器發(fā)送用戶名和密碼。
2、服務(wù)器驗(yàn)證通過(guò)后,在當(dāng)前對(duì)話(session)里面保存相關(guān)數(shù)據(jù),比如用戶角色、登錄
時(shí)間等等。
3、服務(wù)器向用戶返回一個(gè)session_id,寫入用戶的Cookie。
4、用戶隨后的每一次請(qǐng)求,都會(huì)通過(guò)Cookie,將session_id傳回服務(wù)器。5、服務(wù)器收到 session_id,找到前期保存的數(shù)據(jù),由此得知用戶的身份。
這種模式的問(wèn)題在于,擴(kuò)展性(scaling)不好。單機(jī)當(dāng)然沒(méi)有問(wèn)題,如果是服務(wù)器集群,或者是前后端分離的服務(wù)導(dǎo)向架構(gòu),就要求session 數(shù)據(jù)共享,每臺(tái)服務(wù)器都能夠讀取session,
舉例來(lái)說(shuō),A網(wǎng)站和B網(wǎng)站是同一家公司的關(guān)聯(lián)服務(wù)?,F(xiàn)在要求,用戶只要在其中一個(gè)網(wǎng)站登錄,再訪問(wèn)另一個(gè)網(wǎng)站就會(huì)自動(dòng)登錄,請(qǐng)問(wèn)怎么實(shí)現(xiàn)?
一種解決方案是 session 數(shù)據(jù)持久化,寫入數(shù)據(jù)庫(kù)或別的持久層。各種服務(wù)收到請(qǐng)求后,都向持久層請(qǐng)求數(shù)據(jù)。這種方案的優(yōu)點(diǎn)是架構(gòu)清晰,缺點(diǎn)是工程量比較大[]。另外,持久層萬(wàn)一掛了,就會(huì)單點(diǎn)失敗。
另一種方案是服務(wù)器索性不保存 session 數(shù)據(jù)了,所有數(shù)據(jù)都保存在客戶端,每次請(qǐng)求都發(fā)回服務(wù)器。JWT就是這種方案的一個(gè)代表。
JWT:影響了網(wǎng)絡(luò)寬帶
1.2.3 JWT的原理
JWT的原理是,服務(wù)器認(rèn)證以后,生成一個(gè)JSON對(duì)象,發(fā)回給用戶,就像下面這樣。
{
“姓名”:“張三”,
“角色”:“管理員”,
“到期時(shí)間”:“2022年8月1日0點(diǎn)0分”
}
以后,用戶與服務(wù)端通信的時(shí)候,都要發(fā)回這個(gè)JSON對(duì)象。服務(wù)器完全只靠這個(gè)對(duì)象認(rèn)定用戶身份。為了防止用戶篡改數(shù)據(jù),服務(wù)器在生成這個(gè)對(duì)象的時(shí)候,會(huì)加上簽名(詳見后文)。
服務(wù)器就不保存任何 session 數(shù)據(jù)了,也就是說(shuō),服務(wù)器變成無(wú)狀態(tài)了,從而比較容易實(shí)現(xiàn)擴(kuò)展。
1.2.4 JWT的數(shù)據(jù)結(jié)構(gòu)
實(shí)際的 JWT大概就像下面這樣。

它是一個(gè)很長(zhǎng)的字符串,中間用點(diǎn)(.)分隔成三個(gè)部分。注意,JWT內(nèi)部是沒(méi)有換行的,這里只是為了便于展示,將它寫成了幾行
JWT的三個(gè)部分依次如下:
Header(頭部)Payload(負(fù)載,載荷)Signature(簽名)
寫成一行,就是下面的樣子。
Header.Payload.Signature
1.2.4.1 Header
{
"alg": "HS256",
"typ": "JWT"
}JWT頭是一個(gè)描述JWT元數(shù)據(jù)的JSON對(duì)象,alg屬性表示簽名使用的算法,默認(rèn)為HMAC SHA256(寫為HS256);typ屬性表示令牌的類型,JWT令牌統(tǒng)一寫為JWT。最后,使用Base64 URL算法將上述JSON對(duì)象轉(zhuǎn)換為字符串保存 。
1.2.4.2 Payload
Payload 部分也是一個(gè)JSON對(duì)象,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)。JWT規(guī)定了7個(gè)官方字段,供選用。
iss (issuer):簽發(fā)人 exp (expiration time):過(guò)期時(shí)間 sub (subject):主題 aud (audience):受眾 nbf (Not Before):生效時(shí)間 iat (lssued At):簽發(fā)時(shí)間 jti (JWT ID):編號(hào)
除了官方字段,你還可以在這個(gè)部分定義自己的字段,下面就是一個(gè)例子。
{
"sub": "1234567890",
"name" : "John Doe",
“userid”:2
"admin": true
}注意,JWT 默認(rèn)是不加密的,任何人都可以讀到,所以不要把==秘密信息【密碼】==放在這個(gè)部分。這個(gè)JSON 對(duì)象也要使用Base64URL 算法轉(zhuǎn)成字符串。
JWT只是適合在網(wǎng)絡(luò)中傳輸一些非敏感的信息
1.2.4.3 Signature
Signature部分是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改。
首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。然后,使用Header里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名。
HMACSHA256( base64UrlEncode(header) + ".”"+base64UrlEncode(payload), secret)
算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"(.)分隔,就可以返回給用戶。
1.2.5 JWT的使用方式
客戶端收到服務(wù)器返回的JWT,可以儲(chǔ)存在Cookie里面,也可以儲(chǔ)存在 localStorage、SessionStorage
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè)JWT。你可以把它放在Cookie里面自動(dòng)發(fā)送,但是這樣不能跨域,所以更好的做法是放在HTTPs請(qǐng)求的頭信息Authorization字段里面。
客戶端收到服務(wù)器返回的JWT,可以儲(chǔ)存在Cookie里面,也可以儲(chǔ)存在 localStorage。SessionStorage
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè)JWT。你可以把它放在Cookie里面自動(dòng)發(fā)送,但是這樣不能跨域,所以更好的做法是放在HTTP請(qǐng)求的頭信息Authorization字段里面。
步驟
1. 引入jar
<!--引入jwt的依賴-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>2. 創(chuàng)建jwt的工具類
通過(guò)jwt創(chuàng)建token令牌
private static String key="layZhang";
//創(chuàng)建token
public static String createToken(Map<String,Object> map){
//設(shè)置頭部信息
Map<String,Object> head=new HashMap<>();
head.put("alg","HS256");
head.put("typ","JWT");
//設(shè)置發(fā)布日期
Date date=new Date();
//設(shè)置過(guò)期時(shí)間
Calendar instance = Calendar.getInstance();//獲取當(dāng)前時(shí)間
instance.set(Calendar.SECOND,7200);//在當(dāng)前時(shí)間的基礎(chǔ)上添加兩個(gè)小時(shí)
Date time = instance.getTime();//得到Date類型的時(shí)間
//創(chuàng)建token
String token = JWT.create()
.withHeader(head)//設(shè)置頭部信息
.withIssuedAt(date)//設(shè)置發(fā)布時(shí)間
.withExpiresAt(time)//設(shè)置過(guò)期時(shí)間
.withClaim("userInfo", map)//設(shè)置個(gè)人信息
.sign(Algorithm.HMAC256(key));//簽名
return token;
}校驗(yàn)token
//校驗(yàn)token
public static boolean verify(String token){
Verification require = JWT.require(Algorithm.HMAC256(key));
try {
require.build().verify(token);
return true;
}catch (Exception e){
System.out.println("token錯(cuò)誤");
return false;
}
}
JWT.require()方法: 這是JWT庫(kù)中的一個(gè)方法,用于創(chuàng)建一個(gè)Verification【驗(yàn)證】對(duì)象,該對(duì)象用于配置和執(zhí)行JWT的驗(yàn)證過(guò)程。
Algorithm.HMAC256(key): 這里指定了用于簽名JWT的算法和密鑰。HMAC256是一種基于哈希的消息認(rèn)證碼(HMAC)算法,它使用SHA-256哈希函數(shù)。key是一個(gè)密鑰,用于生成和驗(yàn)證JWT的簽名。這個(gè)密鑰在生成JWT令牌和驗(yàn)證JWT令牌時(shí)必須相同。
require.build(): 這個(gè)方法調(diào)用會(huì)基于之前通過(guò)JWT.require(...)方法配置的驗(yàn)證要求,構(gòu)建一個(gè)JWTVerifier實(shí)例。這個(gè)實(shí)例包含了所有必要的驗(yàn)證配置(如簽名算法和密鑰)。
.verify(token): 使用構(gòu)建好的JWTVerifier實(shí)例來(lái)驗(yàn)證給定的token。如果token是有效的(即,它是由指定的密鑰和算法簽名的,且未被篡改),則此方法將成功執(zhí)行。如果token無(wú)效,將拋出異常。
綜上所述,
verify方法通過(guò)指定的密鑰和算法驗(yàn)證給定的JWT令牌是否有效,并根據(jù)驗(yàn)證結(jié)果返回相應(yīng)的布爾值。這種方法是Web應(yīng)用中實(shí)現(xiàn)身份驗(yàn)證和授權(quán)的一種常見方式。
根據(jù)token獲取自定義的信息
//根據(jù)token獲取自定義的信息
public static Map<String,Object> getInfo(String token,String mykey){
JWTVerifier build = JWT.require(Algorithm.HMAC256(key)).build();
Claim claim = build.verify(token).getClaim(mykey);
return claim.asMap();
}
JWT.require(Algorithm.HMAC256(key)): 這部分代碼與前面提到的驗(yàn)證token的方法類似,它創(chuàng)建了一個(gè)Verification配置,指定了用于驗(yàn)證JWT的算法(HMAC256)和密鑰(key)。這里的key應(yīng)該是一個(gè)在JWT生成和驗(yàn)證過(guò)程中都使用的共享密鑰。
.build(): 這個(gè)方法調(diào)用基于前面配置的驗(yàn)證要求,構(gòu)建了一個(gè)JWTVerifier實(shí)例。這個(gè)實(shí)例將用于驗(yàn)證JWT令牌。
build.verify(token): 使用構(gòu)建的JWTVerifier實(shí)例來(lái)驗(yàn)證給定的token。如果token是有效的(即,它確實(shí)是由指定的密鑰和算法簽名的,并且沒(méi)有被篡改),這個(gè)方法將返回一個(gè)DecodedJWT對(duì)象,該對(duì)象包含了JWT中的所有信息。
.getClaim(mykey): 從驗(yàn)證并解碼的JWT中獲取與mykey鍵相關(guān)聯(lián)的Claim對(duì)象。JWT中的信息以鍵值對(duì)的形式存儲(chǔ),其中每個(gè)鍵值對(duì)都是一個(gè)Claim。如果JWT中不存在與mykey對(duì)應(yīng)的Claim,則此方法可能拋出異?;蚍祷?code>null(具體行為取決于JWT庫(kù)的實(shí)現(xiàn))。
claim.asMap(): 如果Claim對(duì)象存在且不為空,這個(gè)方法將Claim中的信息轉(zhuǎn)換為一個(gè)Map。這樣,你就可以像操作普通Map一樣方便地訪問(wèn)JWT中存儲(chǔ)的自定義信息了
1.3 登錄成功后返回json數(shù)據(jù)
AuthenticationSuccessHandler接口:只有一個(gè)抽象方法,為函數(shù)式接口,所以可以使用Lamda表達(dá)式重寫抽象方法。這個(gè)接口是Spring Security用于處理成功認(rèn)證后的行為的一個(gè)鉤子。 用于自定義用戶成功登錄后的處理邏輯。當(dāng)認(rèn)證過(guò)程成功完成時(shí)(例如,用戶提供了正確的用戶名和密碼),Spring Security會(huì)調(diào)用實(shí)現(xiàn)了這個(gè)接口的類的
onAuthenticationSuccess方法。
private AuthenticationSuccessHandler successHandler(){
// return new AuthenticationSuccessHandler() {
// @Override //
// public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// //設(shè)置響應(yīng)的編碼
// httpServletResponse.setContentType("application/json;charset=utf-8");
// //獲取輸出對(duì)象
// PrintWriter writer = httpServletResponse.getWriter();
// //返回json數(shù)據(jù)即可
// Map<String,Object> map=new HashMap<>();
// map.put("username",authentication.getName());
// Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// //獲取權(quán)限
// List<String> collect = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());
//
// map.put("permissions",collect);
// String token = JWTUtil.createToken(map);
//
// //返回一個(gè)統(tǒng)一的json對(duì)象
// R r=new R(200,"登錄成功",token);
// //轉(zhuǎn)換為json字符串
// String jsonString = JSON.toJSONString(r);
// //servlet
// writer.println(jsonString);
// writer.flush();
// writer.close();
// }
// };
//使用Lambda表達(dá)式
return (httpServletRequest, httpServletResponse, authentication) -> {
//設(shè)置響應(yīng)的編碼
httpServletResponse.setContentType("application/json;charset=utf-8");
//獲取輸出對(duì)象
PrintWriter writer = httpServletResponse.getWriter();
//返回json數(shù)據(jù)即可
Map<String,Object> map=new HashMap<>();
map.put("username",authentication.getName());
//獲取權(quán)限信息列表
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//獲取響應(yīng)的權(quán)限標(biāo)識(shí)符
List<String> collect = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());
map.put("permissions",collect);
String token = JWTUtil.createToken(map);
//返回一個(gè)統(tǒng)一的json對(duì)象
R r=new R(200,"登錄成功",token);
//轉(zhuǎn)換為json字符串
String jsonString = JSON.toJSONString(r);
//servlet
writer.println(jsonString);
writer.flush();
writer.close();
};
}修改配置
.successHandler(successHandler())
onAuthenticationSuccess方法:
這是AuthenticationSuccessHandler接口中需要實(shí)現(xiàn)的方法。它有三個(gè)參數(shù):HttpServletRequest、HttpServletResponse和Authentication。
HttpServletRequest: 提供了對(duì)當(dāng)前HTTP請(qǐng)求信息的訪問(wèn)。HttpServletResponse: 允許你控制對(duì)客戶端的響應(yīng),比如設(shè)置響應(yīng)頭、發(fā)送響應(yīng)體等。Authentication: 包含了認(rèn)證成功的用戶的信息,比如用戶名、密碼(通常加密或散列)、權(quán)限等。
設(shè)置響應(yīng)編碼和獲取輸出對(duì)象:
httpServletResponse.setContentType("application/json;charset=utf-8");: 設(shè)置響應(yīng)的內(nèi)容類型為JSON,并指定字符集為UTF-8。PrintWriter writer = httpServletResponse.getWriter();: 獲取一個(gè)PrintWriter對(duì)象,用于向客戶端發(fā)送字符文本數(shù)據(jù)。
構(gòu)建返回的JSON數(shù)據(jù):
- 創(chuàng)建一個(gè)
HashMap來(lái)存儲(chǔ)要返回給客戶端的數(shù)據(jù)。 - 從
Authentication對(duì)象中獲取用戶名并添加到map中。 - 使用Java 8的流(Stream)從
Authentication對(duì)象中獲取用戶的權(quán)限(GrantedAuthority),并將它們轉(zhuǎn)換為字符串列表,然后添加到map中。
獲取權(quán)限標(biāo)識(shí)符:
authorities是一個(gè)Collection類型的集合,它包含了用戶所擁有的權(quán)限信息。每個(gè)GrantedAuthority對(duì)象都代表了一個(gè)權(quán)限,通常是通過(guò)它的getAuthority()方法來(lái)獲取權(quán)限的標(biāo)識(shí)符(通常是一個(gè)字符串)。使用 Java 8 的 Stream API,您可以將這個(gè)集合轉(zhuǎn)換為一個(gè)新的
List,其中包含了所有權(quán)限的標(biāo)識(shí)符。這是通過(guò)以下步驟實(shí)現(xiàn)的:
- 調(diào)用
stream()方法:將Collection轉(zhuǎn)換為一個(gè)Stream,這樣您就可以使用 Stream API 提供的各種操作了。 - 調(diào)用
map()方法:map()方法接受一個(gè)函數(shù)作為參數(shù),這個(gè)函數(shù)會(huì)被應(yīng)用到 Stream 中的每個(gè)元素上。在這個(gè)例子中,您傳遞了一個(gè) lambda 表達(dá)式item -> item.getAuthority(),它將每個(gè)GrantedAuthority對(duì)象映射為其權(quán)限標(biāo)識(shí)符(即調(diào)用getAuthority()方法的結(jié)果)。這樣,Stream 中的元素就從GrantedAuthority對(duì)象變成了字符串。 - 調(diào)用
collect()方法:collect()方法是一個(gè)終端操作,它接受一個(gè)Collector來(lái)將 Stream 中的元素累積成一個(gè)結(jié)果。在這個(gè)例子中,您使用了Collectors.toList()來(lái)收集 Stream 中的所有元素到一個(gè)新的List中。
這行代碼的作用就是:將用戶所擁有的所有權(quán)限(
GrantedAuthority對(duì)象)轉(zhuǎn)換為一個(gè)包含這些權(quán)限標(biāo)識(shí)符(字符串)的列表。
構(gòu)建統(tǒng)一的響應(yīng)對(duì)象并轉(zhuǎn)換為JSON字符串:
- 創(chuàng)建一個(gè)
R對(duì)象(假設(shè)這是一個(gè)自定義的響應(yīng)類,用于封裝響應(yīng)的狀態(tài)碼、消息和數(shù)據(jù)),將狀態(tài)碼設(shè)置為200,消息設(shè)置為"登錄成功!",并將JWT令牌作為數(shù)據(jù)設(shè)置進(jìn)去。 - 使用某個(gè)JSON庫(kù)(如Fastjson、Jackson等)將
R對(duì)象轉(zhuǎn)換為JSON字符串。
發(fā)送響應(yīng)到客戶端:
- 使用
PrintWriter將JSON字符串寫入到HTTP響應(yīng)中。 - 調(diào)用
flush()方法確保所有緩沖的輸出都被發(fā)送到客戶端。 - 調(diào)用
close()方法關(guān)閉PrintWriter。
總的來(lái)說(shuō),這個(gè)
successHandler方法在用戶成功登錄后,會(huì)構(gòu)建一個(gè)包含用戶名、權(quán)限和JWT令牌的JSON響應(yīng),并將其發(fā)送給客戶端。這樣,客戶端就可以使用這個(gè)JWT令牌進(jìn)行后續(xù)的身份驗(yàn)證和授權(quán)操作。
1.4 登錄失敗返回的json數(shù)據(jù)
//登錄失敗返回json數(shù)據(jù)
private AuthenticationFailureHandler failureHandler(){
return (httpServletRequest, httpServletResponse, e)->{
//設(shè)置編碼
httpServletResponse.setContentType("application/json;charset=utf-8");
//獲取輸出對(duì)象
PrintWriter writer = httpServletResponse.getWriter();
R r=new R(500,"登錄失?。?,e.getMessage());
String s = JSON.toJSONString(r);
writer.println(s);
writer.flush();
writer.close();
};
}修改配置
.failureHandler(failureHandler())
1.5 權(quán)限不足返回的json數(shù)據(jù)
//權(quán)限不足返回json數(shù)據(jù)
private AccessDeniedHandler accessDeniedHandler(){
return (httpServletRequest, httpServletResponse, e)->{
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
R r=new R(403,"權(quán)限不足,請(qǐng)聯(lián)系管理員",e.getMessage());
String s = JSON.toJSONString(r);
writer.println(s);
writer.flush();
writer.close();
};
}修改配置
//指定權(quán)限不足跳轉(zhuǎn)的頁(yè)面
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());1.6 未登錄訪問(wèn)資源返回json數(shù)據(jù)
需要自定義一個(gè)過(guò)濾器
@Component //交于spring容器管理
public class LoginFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//若是登錄路徑,放行
String requestURI = httpServletRequest.getRequestURI();
String method = httpServletRequest.getMethod();
if("/login".equals(requestURI)&&"POST".equals(method)){
//放行
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
//統(tǒng)一編碼格式
httpServletResponse.setContentType("application/json;charset=utf-8");
//1. 從請(qǐng)求頭中獲取token令牌
String token = httpServletRequest.getHeader("token");
//2. 判斷token是否為null
if(StringUtils.isEmpty(token)){
//獲取傳輸對(duì)象
PrintWriter writer = httpServletResponse.getWriter();
R r =new R(500,"未登錄",null);
String s = JSON.toJSONString(r);
writer.write(s);
writer.flush();
writer.close();
return;
}
//3. 驗(yàn)證token
if(!JWTUtil.verify(token)){
PrintWriter writer = httpServletResponse.getWriter();
//返回一個(gè)token失效的json數(shù)據(jù)
R r=new R(500,"token失效!",null);
String s = JSON.toJSONString(r);
writer.write(s);
writer.flush();
writer.close();
return;
}
//把當(dāng)前用戶的信息封裝到Authentication對(duì)象中
SecurityContext context = SecurityContextHolder.getContext();
Map<String, Object> userInfo = JWTUtil.getInfo(token, "userInfo");
Object username = userInfo.get("username");
//權(quán)限標(biāo)識(shí)符
// List<String> permissions = (List<String>) userInfo.get("permissions");
List<String> permissions = (List<String>) userInfo.get("permission");
//通過(guò)stream流轉(zhuǎn)換類型
List<SimpleGrantedAuthority> collect = permissions.stream().map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
// List<SimpleGrantedAuthority> collect = permissions.stream().map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
//三個(gè)參數(shù)
//Object principal,賬號(hào)
//Object credentials,密碼 null
//Collection<? extends GrantedAuthority> authorities:權(quán)限
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(username,null,collect);
context.setAuthentication(usernamePasswordAuthenticationToken);
//放行
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}用于處理HTTP請(qǐng)求的過(guò)濾器方法,通常用于在請(qǐng)求到達(dá)控制器之前執(zhí)行一些預(yù)處理操作
檢查登錄路徑并放行:
- 首先,方法通過(guò)檢查請(qǐng)求的URI和方法來(lái)確定是否是一個(gè)登錄請(qǐng)求(通常是
/login路徑且方法為POST)。 - 如果是登錄請(qǐng)求,則直接調(diào)用
filterChain.doFilter(httpServletRequest, httpServletResponse);來(lái)放行請(qǐng)求,允許它繼續(xù)通過(guò)過(guò)濾器鏈到達(dá)相應(yīng)的控制器。
從請(qǐng)求頭中獲取Token:
通過(guò)
httpServletRequest.getHeader("token")從HTTP請(qǐng)求頭中獲取名為token的值,這個(gè)值通常是一個(gè)JWT(JSON Web Token),用于身份驗(yàn)證和授權(quán)。
檢查Token是否為空:
- 使用
StringUtils.isEmpty(token)(這里假設(shè)StringUtils是一個(gè)工具類,用于字符串操作)來(lái)檢查Token是否為空或null。 - 如果Token為空,則構(gòu)造一個(gè)包含錯(cuò)誤信息的JSON響應(yīng)(狀態(tài)碼500,消息“未登錄”),并寫入響應(yīng)體中,然后結(jié)束方法執(zhí)行。
驗(yàn)證Token:
- 調(diào)用
JWTUtil.verify(token)(這里假設(shè)JWTUtil是一個(gè)工具類,用于處理JWT)來(lái)驗(yàn)證Token的有效性。 - 如果Token無(wú)效(例如,簽名不匹配、過(guò)期等),則構(gòu)造一個(gè)包含錯(cuò)誤信息的JSON響應(yīng)(狀態(tài)碼500,消息“token失效!”),并寫入響應(yīng)體中,然后結(jié)束方法執(zhí)行。
從Token中提取用戶信息并封裝:
- 使用
JWTUtil.getInfo(token, "userInfo")(這里假設(shè)getInfo方法從Token中提取特定字段的信息,"userInfo"是字段名)從Token中提取用戶信息。 - 從用戶信息中提取用戶名和權(quán)限列表。注意,這里權(quán)限列表的鍵名從
permissions更改為permission,這取決于Token中實(shí)際存儲(chǔ)的鍵名。 - 使用Java 8的Stream API將權(quán)限列表中的每個(gè)權(quán)限字符串轉(zhuǎn)換為
SimpleGrantedAuthority對(duì)象,這些對(duì)象代表了Spring Security中的權(quán)限。
將用戶信息封裝到
Authentication對(duì)象中創(chuàng)建一個(gè)
UsernamePasswordAuthenticationToken(或其他適合的Authentication子類)實(shí)例,設(shè)置用戶名、密碼(對(duì)于JWT通常不需要,但可以使用null或特殊值)、權(quán)限列表等,并將該實(shí)例設(shè)置到SecurityContextHolder中
設(shè)置安全上下文:
通過(guò)
SecurityContextHolder.getContext().setAuthentication(...)將身份驗(yàn)證信息設(shè)置到當(dāng)前線程的安全上下文中。這是必要的,因?yàn)镾pring Security會(huì)在后續(xù)的處理過(guò)程中(如訪問(wèn)控制決策)檢查這個(gè)上下文來(lái)確定當(dāng)前用戶的身份和權(quán)限。
修改配置類
在配置類中注入自定義的過(guò)濾器
在方法中將自定義的過(guò)濾器放在之前
//把自定義的過(guò)濾器放在UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);到此這篇關(guān)于使用JWT的SpringSecurity實(shí)現(xiàn)前后端分離的文章就介紹到這了,更多相關(guān)SpringSecurity JWT前后端分離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity多表多端賬戶登錄的實(shí)現(xiàn)
- SpringSecurity集成第三方登錄過(guò)程詳解(最新推薦)
- springsecurity實(shí)現(xiàn)用戶登錄認(rèn)證快速使用示例代碼(前后端分離項(xiàng)目)
- SpringSecurity自動(dòng)登錄流程與實(shí)現(xiàn)詳解
- SpringSecurity6自定義JSON登錄的實(shí)現(xiàn)
- SpringSecurity6.x多種登錄方式配置小結(jié)
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- SpringSecurity角色權(quán)限控制(SpringBoot+SpringSecurity+JWT)
- SpringBoot3.0+SpringSecurity6.0+JWT的實(shí)現(xiàn)
- springSecurity之如何添加自定義過(guò)濾器
- springSecurity自定義登錄接口和JWT認(rèn)證過(guò)濾器的流程
相關(guān)文章
Java在并發(fā)環(huán)境中SimpleDateFormat多種解決方案
這篇文章主要介紹了Java在并發(fā)環(huán)境中SimpleDateFormat多種解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
說(shuō)明Java的傳遞與回調(diào)機(jī)制的代碼示例分享
這篇文章主要介紹了說(shuō)明Java的傳遞與回調(diào)機(jī)制的代碼示例分享,傳遞與回調(diào)機(jī)制是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09
Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的長(zhǎng)輪詢的示例代碼
長(zhǎng)輪詢是與服務(wù)器保持即時(shí)通信的最簡(jiǎn)單的方式,它不使用任何特定的協(xié)議,例如 WebSocket ,所以也不依賴于瀏覽器版本等外部條件的兼容性。本文將用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的長(zhǎng)輪詢,需要的可以參考一下2022-08-08
MyBatisPlus PaginationInterceptor分頁(yè)插件的使用詳解
這篇文章主要介紹了MyBatisPlus PaginationInterceptor分頁(yè)插件的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
SpringCloud微服務(wù)基礎(chǔ)簡(jiǎn)介
今天帶大家學(xué)習(xí)一下SpringCloud微服務(wù)的相關(guān)知識(shí),文中有非常詳細(xì)的圖文示例及介紹,對(duì)正在學(xué)習(xí)SpringCloud微服務(wù)的小伙伴們很有幫助哦,需要的朋友可以參考下2021-05-05
Spring?Boot參數(shù)校驗(yàn)的8大坑與生產(chǎn)級(jí)避坑完整指南
在項(xiàng)目開發(fā)中,時(shí)常會(huì)碰到前端傳遞過(guò)來(lái)的請(qǐng)求參數(shù)需要校驗(yàn),畢竟永遠(yuǎn)不要相信沒(méi)有經(jīng)過(guò)自己校驗(yàn)的數(shù)據(jù),這篇文章主要介紹了Spring?Boot參數(shù)校驗(yàn)的8大坑與生產(chǎn)級(jí)避坑的相關(guān)資料,需要的朋友可以參考下2025-09-09
@Transactional遇到try catch失效的問(wèn)題
這篇文章主要介紹了@Transactional遇到try catch失效的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01

