JwtAuthenticationConverter
定义与作用
JwtAuthenticationConverter 是 Spring Security OAuth2 Resource Server 中的关键转换器,负责将 JwtDecoder 解码后的 Jwt 对象(包含 Header 和 Claims)转换为 Spring Security 能够识别的 Authentication 对象(通常是 JwtAuthenticationToken)。
如果说 JwtDecoder 解决的是"Token 是否真实有效",那么 JwtAuthenticationConverter 解决的就是"Token 中包含的权限如何映射到 Spring Security 的权限体系"。JWT 中的 Claims 名称和格式由签发方决定,而 Spring Security 的授权决策依赖 GrantedAuthority 集合,两者之间的桥梁就是 JwtAuthenticationConverter。
核心原理
转换流程
默认行为
Spring Security 提供的默认 JwtAuthenticationConverter 包含一个默认的 JwtGrantedAuthoritiesConverter,其行为如下:
- Principal 提取:默认取 JWT 的
subClaim 作为Authentication.getPrincipal() - 权限提取:默认从
scope或scpClaim 中提取权限 - 前缀处理:默认给每个提取的权限添加
SCOPE_前缀
例如 JWT Payload 为:
{
"sub": "user123",
"scope": "read write",
"iss": "auth-server"
}
默认转换后的权限为:SCOPE_read、SCOPE_write
自定义扩展点
| 组件 | 方法 | 用途 |
|---|---|---|
JwtAuthenticationConverter | setJwtGrantedAuthoritiesConverter() | 替换权限提取逻辑 |
JwtAuthenticationConverter | setPrincipalClaimName() | 指定 Principal 来源 Claim |
JwtGrantedAuthoritiesConverter | setAuthoritiesClaimName() | 指定权限来源 Claim |
JwtGrantedAuthoritiesConverter | setAuthorityPrefix() | 修改权限前缀 |
示例一:默认 Scope 权限提取
场景:OAuth2 授权服务器签发标准 Token,使用 scope 表示权限范围,资源服务器使用默认配置。
JWT 内容:
{
"sub": "user123",
"scope": "read write",
"iss": "https://auth-server.example.com",
"exp": 1718000000
}
操作配置:
@EnableWebSecurity
public class ScopeResourceServerConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/articles").hasAuthority("SCOPE_read")
.antMatchers(HttpMethod.POST, "/api/articles").hasAuthority("SCOPE_write")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt(); // 使用默认 JwtAuthenticationConverter
}
}
结果分析:
JwtGrantedAuthoritiesConverter从scopeClaim 中提取"read"和"write"- 自动添加
SCOPE_前缀,最终权限列表为[SCOPE_read, SCOPE_write] - GET
/api/articles需要SCOPE_read,与 Token 权限匹配,访问成功 - POST
/api/articles需要SCOPE_write,与 Token 权限匹配,访问成功 - 若 Token 只有
scope: "read",则 POST 请求返回 403
示例二:自定义 Authorities 映射(从 roles Claim 提取 ROLE_ 权限)
场景:内部授权服务器签发 JWT 时使用 roles Claim 存放角色信息(如 ["USER", "ADMIN"]),需要映射为 Spring Security 的 ROLE_USER、ROLE_ADMIN 权限。
JWT 内容:
{
"sub": "admin001",
"roles": ["ADMIN", "USER"],
"department": "技术部",
"iss": "https://internal-auth.example.com",
"exp": 1718000000
}
操作前配置(默认配置,权限丢失):
@EnableWebSecurity
public class DefaultConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.and()
.oauth2ResourceServer()
.jwt(); // 默认只提取 scope,roles 被忽略
}
}
问题:admin001 携带的 JWT 包含 roles: ["ADMIN"],但默认 JwtGrantedAuthoritiesConverter 只读取 scope Claim。由于 JWT 中没有 scope,转换后的权限列表为空,导致访问 /api/admin/** 时返回 403。
操作后配置(自定义 Converter):
@EnableWebSecurity
public class CustomRoleResourceServerConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
// 1. 配置权限提取器
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter();
// 从 "roles" Claim 提取权限(而非默认的 "scope")
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
// 添加 "ROLE_" 前缀,使 hasRole("ADMIN") 能匹配
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
// 2. 配置主转换器
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
// 可选:指定 Principal 从 "sub" 提取(默认就是 sub)
converter.setPrincipalClaimName("sub");
return converter;
}
}
结果分析:
setAuthoritiesClaimName("roles")告诉提取器从rolesClaim 读取权限setAuthorityPrefix("ROLE_")将"ADMIN"转换为"ROLE_ADMIN",与hasRole("ADMIN")完美匹配admin001的 JWT 被转换为权限[ROLE_ADMIN, ROLE_USER],访问/api/admin/**成功- 若普通用户 JWT 为
roles: ["USER"],则只能访问/api/user/**,访问/api/admin/**返回 403
易错场景:Claims 名称不一致导致权限丢失
面试高频考点
问题现象:明明 JWT 中包含用户角色信息,但 Spring Security 始终判定为无权限,返回 403。
常见错误对比:
| 错误场景 | JWT 实际内容 | 代码配置 | 结果 |
|---|---|---|---|
| Claim 名称不匹配 | "roles": ["ADMIN"] | setAuthoritiesClaimName("authorities") | 提取不到权限,403 |
| 前缀不匹配 | "scope": "read" | 默认配置,但使用 hasRole("read") | hasRole 实际检查 ROLE_read,而权限是 SCOPE_read,403 |
| 前缀遗漏 | "roles": ["ADMIN"] | setAuthoritiesClaimName("roles"),但忘记 setAuthorityPrefix("ROLE_") | 权限为 ADMIN,hasRole("ADMIN") 检查 ROLE_ADMIN,403 |
| 数据类型错误 | "roles": "ADMIN"(字符串) | 期望数组提取 | 提取器可能无法正确解析,权限为空 |
排查步骤:
- 打印实际转换后的权限:
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter granted = new JwtGrantedAuthoritiesConverter();
granted.setAuthoritiesClaimName("roles");
granted.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
Collection<GrantedAuthority> authorities = granted.convert(jwt);
// 调试打印
System.out.println("JWT sub: " + jwt.getSubject());
System.out.println("Raw claims: " + jwt.getClaims().get("roles"));
System.out.println("Converted authorities: " + authorities);
return authorities;
});
return converter;
}
- 确认 hasRole 与 hasAuthority 的区别:
// 以下两种写法等价:
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")
// 如果使用 hasRole,权限必须带 ROLE_ 前缀
// 如果使用 hasAuthority,需写完整权限字符串
核心要点:
JwtGrantedAuthoritiesConverter的setAuthoritiesClaimName()必须与 JWT 签发方的 Claim 名称完全一致(区分大小写)hasRole("XXX")内部自动拼接为"ROLE_XXX",因此setAuthorityPrefix("ROLE_")必须配合hasRole使用- 如果签发方在 JWT 中已包含
ROLE_前缀(如"roles": ["ROLE_ADMIN"]),则应设置setAuthorityPrefix(""),避免重复前缀变成ROLE_ROLE_ADMIN