SpringCloud實(shí)現(xiàn)權(quán)限管理(網(wǎng)關(guān)+jwt版)
首先要想一個(gè)問(wèn)題:為什么微服務(wù)不能像普通的Spring Boot項(xiàng)目一樣鑒權(quán)?其實(shí)并不是不能,而是不適合。 在微服務(wù)架構(gòu)中使用 Spring Security + RBAC/JET 時(shí),會(huì)面臨 "重復(fù)鑒權(quán)" 的核心痛點(diǎn)。下面從技術(shù)原理到解決方案完整解析這個(gè)問(wèn)題。
問(wèn)題本質(zhì):為什么會(huì)有重復(fù)鑒權(quán)?
傳統(tǒng)單體架構(gòu)流程

所有權(quán)限校驗(yàn)集中在一個(gè)應(yīng)用內(nèi)完成 • 用戶登錄后,Session或Token在單應(yīng)用中全局有效
微服務(wù)架構(gòu)下的流程

每個(gè)微服務(wù)都需要獨(dú)立完成:
- 解析Token
- 查詢數(shù)據(jù)庫(kù)驗(yàn)證權(quán)限
- 構(gòu)建SecurityContext
假設(shè)有3個(gè)服務(wù)鏈?zhǔn)秸{(diào)用:
客戶端 → 網(wǎng)關(guān) → 服務(wù)A → 服務(wù)B → 服務(wù)C 每個(gè)服務(wù)都重復(fù): 查詢用戶數(shù)據(jù)(1次DB查詢) 查詢權(quán)限數(shù)據(jù)(1次DB查詢) ??總查詢次數(shù) = 3服務(wù) × 2查詢 = 6次? • 在服務(wù)交叉的時(shí)候,導(dǎo)致 多次數(shù)據(jù)庫(kù)查詢 和 重復(fù)計(jì)算,使數(shù)據(jù)庫(kù)壓力倍增和網(wǎng)絡(luò)開(kāi)銷(xiāo)疊加。
(1)創(chuàng)建 JWT 工具類(lèi)
在util模塊下創(chuàng)建JWT 工具類(lèi)
@Component
public class JwtTokenUtil {
// 密鑰,用于簽名和驗(yàn)證 JWT,應(yīng)妥善保管
@Value("${jwt.secret}")
private String secret;
// JWT 的過(guò)期時(shí)間,這里設(shè)置為 10 小時(shí)
@Value("${jwt.expiration}")
private Long expiration;
// 根據(jù)用戶詳細(xì)信息生成 JWT
public String generateToken(SysRoleNameUserId sysRoleNameUserId) {
//自定義的聲明
Map<String, Object> claims = new HashMap<>();
//鑒權(quán)所需的權(quán)限角色
claims.put("identities",sysRoleNameUserId.getRoleNames());
return createToken(claims, String.valueOf(sysRoleNameUserId.getUserId()));
}
// 創(chuàng)建 JWT 的具體方法
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)//自定義的聲明
.setSubject(subject)//存的用戶id
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
//解析jwt
public Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(secret)
.build()
.parseClaimsJws(token)
.getBody();
}
}
JWT 的密鑰(jwt.secret)應(yīng)通過(guò)配置中心(如 Nacos)或環(huán)境變量注入,避免硬編碼。這里選擇環(huán)境變量注入: 在util模塊下的application.yml配置文件里面指定
jwt: secret: your-secret-key-here-must-be-at-least-256-bits-long expiration: 1000 * 60 * 60 * 10 # 10 hour
(2)創(chuàng)建JwtFilter
在gateway網(wǎng)關(guān)模塊創(chuàng)建JwtFilter過(guò)濾器來(lái)驗(yàn)證并解析jwt,從而獲取權(quán)限信息
@Component
public class JwtFilter implements GlobalFilter, Ordered {
@Resource
private JwtTokenUtil jwtTokenUtil;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
List<String> authHeader = headers.get("Authorization");
if (authHeader != null && authHeader.get(0).startsWith("Bearer ")) {
String token = authHeader.get(0).substring(7);
// 驗(yàn)證 JWT 并提取角色信息
Claims claims = null;
try{
claims = jwtTokenUtil.extractAllClaims(token);
}catch (IllegalArgumentException e) {
//解析 JWT 時(shí)發(fā)生其他錯(cuò)誤
System.out.println("解析 token 時(shí)發(fā)生其他錯(cuò)誤");
} catch (ExpiredJwtException e) {
//JWT 已過(guò)期
System.out.println("token 已過(guò)期");
}
//取出權(quán)限角色列表
Object identitiesObj = claims.get("identities");
List<GrantedAuthority> authorities = new ArrayList<>();
if (identitiesObj instanceof List<?>) {
for (Object role : (List<?>) identitiesObj) {
if (role instanceof String) {
// 將字符串轉(zhuǎn)換為 SimpleGrantedAuthority
authorities.add(new SimpleGrantedAuthority((String) role));
}
}
}
String userId = claims.getSubject();
//將 Authentication 對(duì)象設(shè)置到 SecurityContextHolder 中后,Spring Security 就能在后續(xù)的授權(quán)過(guò)程中使用這些權(quán)限信息了。
Authentication authentication = new UsernamePasswordAuthenticationToken(userId, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
(3)配置 Spring Security
在網(wǎng)關(guān)gateway模塊配置所有微服務(wù)整體的權(quán)限管理規(guī)則:
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.csrf().disable()// 關(guān)閉 CSRF(跨站請(qǐng)求偽造)防護(hù)。在無(wú)狀態(tài) API(如 JWT 場(chǎng)景)中,CSRF 防護(hù)不必要(通常依賴 Authorization 頭而非 Cookie)。 避免對(duì) POST、PUT 等請(qǐng)求要求攜帶 CSRF Token。
.authorizeExchange()
.pathMatchers("/api/product/**").permitAll()
.pathMatchers("/admin/**").permitAll()
.anyExchange().authenticated();
return http.build();
}
}
如果各個(gè)微服務(wù)還需要獨(dú)自的更細(xì)粒度的權(quán)限控制,只需要在單個(gè)微服務(wù)模塊中單獨(dú)配置一個(gè)Spring Security就行了。
到此這篇關(guān)于SpringCloud實(shí)現(xiàn)權(quán)限管理(網(wǎng)關(guān)+jwt版)的文章就介紹到這了,更多相關(guān)SpringCloud 權(quán)限管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java開(kāi)發(fā)中為什么雙重效驗(yàn)鎖要加volatile
這篇文章主要為大家介紹了java開(kāi)發(fā)中為什么雙重效驗(yàn)鎖要加volatile原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Java使用JCommander實(shí)現(xiàn)解析命令行參數(shù)
jcommander?是一個(gè)只有幾十?kb?的?Java?命令行參數(shù)解析工具,可以通過(guò)注解的方式快速實(shí)現(xiàn)命令行參數(shù)解析,本文就來(lái)和大家介紹一下JCommander是如何解析命令行參數(shù)吧2023-06-06
java使用Runtime執(zhí)行系統(tǒng)命令遇到的問(wèn)題
這篇文章主要介紹了java使用Runtime執(zhí)行系統(tǒng)命令遇到的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
java匿名內(nèi)部類(lèi)實(shí)例代碼詳解
這篇文章主要介紹了java匿名內(nèi)部類(lèi)實(shí)例代碼詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Java如何通過(guò)反射取實(shí)體類(lèi)字段取值
這篇文章主要介紹了Java如何通過(guò)反射取實(shí)體類(lèi)字段取值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
掌握SpringMVC中@InitBinder的實(shí)際應(yīng)用
這篇文章主要介紹了掌握SpringMVC中@InitBinder的實(shí)際應(yīng)用,@InitBinder是Spring MVC框架中的一個(gè)注解,用于自定義數(shù)據(jù)綁定的方法,通過(guò)在控制器中使用@InitBinder注解,可以將特定的數(shù)據(jù)綁定邏輯應(yīng)用于請(qǐng)求參數(shù)的處理過(guò)程中,需要的朋友可以參考下2023-10-10
SpringMVC如何獲取多種類(lèi)型數(shù)據(jù)響應(yīng)
這篇文章主要介紹了SpringMVC如何獲取多種類(lèi)型數(shù)據(jù)響應(yīng),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11

