首先要想一个问题:为什么微服务不能像普通的spring boot项目一样鉴权?其实并不是不能,而是不适合。 在微服务架构中使用 spring security + rbac/jet 时,会面临 "重复鉴权" 的核心痛点。下面从技术原理到解决方案完整解析这个问题。
问题本质:为什么会有重复鉴权?
传统单体架构流程

所有权限校验集中在一个应用内完成 • 用户登录后,session或token在单应用中全局有效
微服务架构下的流程

每个微服务都需要独立完成:
- 解析token
- 查询数据库验证权限
- 构建securitycontext
假设有3个服务链式调用:
客户端 → 网关 → 服务a → 服务b → 服务c 每个服务都重复: 查询用户数据(1次db查询) 查询权限数据(1次db查询) 总查询次数 = 3服务 × 2查询 = 6次 • 在服务交叉的时候,导致 多次数据库查询 和 重复计算,使数据库压力倍增和网络开销叠加。
(1)创建 jwt 工具类
在util模块下创建jwt 工具类
@component
public class jwttokenutil {
// 密钥,用于签名和验证 jwt,应妥善保管
@value("${jwt.secret}")
private string secret;
// jwt 的过期时间,这里设置为 10 小时
@value("${jwt.expiration}")
private long expiration;
// 根据用户详细信息生成 jwt
public string generatetoken(sysrolenameuserid sysrolenameuserid) {
//自定义的声明
map<string, object> claims = new hashmap<>();
//鉴权所需的权限角色
claims.put("identities",sysrolenameuserid.getrolenames());
return createtoken(claims, string.valueof(sysrolenameuserid.getuserid()));
}
// 创建 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)应通过配置中心(如 nacos)或环境变量注入,避免硬编码。这里选择环境变量注入: 在util模块下的application.yml配置文件里面指定
jwt: secret: your-secret-key-here-must-be-at-least-256-bits-long expiration: 1000 * 60 * 60 * 10 # 10 hour
(2)创建jwtfilter
在gateway网关模块创建jwtfilter过滤器来验证并解析jwt,从而获取权限信息
@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);
// 验证 jwt 并提取角色信息
claims claims = null;
try{
claims = jwttokenutil.extractallclaims(token);
}catch (illegalargumentexception e) {
//解析 jwt 时发生其他错误
system.out.println("解析 token 时发生其他错误");
} catch (expiredjwtexception e) {
//jwt 已过期
system.out.println("token 已过期");
}
//取出权限角色列表
object identitiesobj = claims.get("identities");
list<grantedauthority> authorities = new arraylist<>();
if (identitiesobj instanceof list<?>) {
for (object role : (list<?>) identitiesobj) {
if (role instanceof string) {
// 将字符串转换为 simplegrantedauthority
authorities.add(new simplegrantedauthority((string) role));
}
}
}
string userid = claims.getsubject();
//将 authentication 对象设置到 securitycontextholder 中后,spring security 就能在后续的授权过程中使用这些权限信息了。
authentication authentication = new usernamepasswordauthenticationtoken(userid, null, authorities);
securitycontextholder.getcontext().setauthentication(authentication);
}
return chain.filter(exchange);
}
@override
public int getorder() {
return -1;
}
}
(3)配置 spring security
在网关gateway模块配置所有微服务整体的权限管理规则:
@configuration
@enablewebfluxsecurity
public class securityconfig {
@bean
public securitywebfilterchain securitywebfilterchain(serverhttpsecurity http) {
http.csrf().disable()// 关闭 csrf(跨站请求伪造)防护。在无状态 api(如 jwt 场景)中,csrf 防护不必要(通常依赖 authorization 头而非 cookie)。 避免对 post、put 等请求要求携带 csrf token。
.authorizeexchange()
.pathmatchers("/api/product/**").permitall()
.pathmatchers("/admin/**").permitall()
.anyexchange().authenticated();
return http.build();
}
}
如果各个微服务还需要独自的更细粒度的权限控制,只需要在单个微服务模块中单独配置一个spring security就行了。
到此这篇关于springcloud实现权限管理(网关+jwt版)的文章就介绍到这了,更多相关springcloud 权限管理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论