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ò)開銷疊加。
(1)創(chuàng)建 JWT 工具類
在util模塊下創(chuàng)建JWT 工具類
@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報(bào)錯(cuò):java.lang.UnsatisfiedLinkError問(wèn)題的解決辦法
在Java開發(fā)中,java.lang.UnsatisfiedLinkError是一種與本地方法調(diào)用相關(guān)的常見異常,本文將詳細(xì)分析這一異常的背景、可能的原因、錯(cuò)誤代碼示例、正確代碼示例,以及編寫代碼時(shí)需要注意的事項(xiàng),需要的朋友可以參考下2024-09-09
SpringBoot 多數(shù)據(jù)源及事務(wù)解決方案小結(jié)
本文主要介紹了多數(shù)據(jù)源管理的解決方案(應(yīng)用層事務(wù),而非XA二段提交保證),以及對(duì)多個(gè)庫(kù)同時(shí)操作的事務(wù)管理,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06
java中struts2實(shí)現(xiàn)文件上傳下載功能
這篇文章主要介紹了java中struts2實(shí)現(xiàn)文件上傳下載功能的方法,以實(shí)例形式分析了struts2文件上傳下載功能的實(shí)現(xiàn)技巧與相關(guān)問(wèn)題,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2016-05-05
Java實(shí)現(xiàn)Linux下雙守護(hù)進(jìn)程
這篇文章主要介紹了Java實(shí)現(xiàn)Linux下雙守護(hù)進(jìn)程的思路、原理以及具體實(shí)現(xiàn)方式,非常的詳細(xì),希望對(duì)大家有所幫助2014-10-10
如何利用Java獲取當(dāng)天的開始和結(jié)束時(shí)間
這篇文章主要介紹了如何使用Java?8的LocalDate和LocalDateTime類獲取指定日期的開始和結(jié)束時(shí)間,展示了如何通過(guò)這些類進(jìn)行日期和時(shí)間的處理,從而簡(jiǎn)化了日期時(shí)間操作,需要的朋友可以參考下2025-02-02
Java線程隊(duì)列LinkedBlockingQueue的使用
本文主要介紹了Java線程隊(duì)列LinkedBlockingQueue的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06

