乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

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,其行为如下:

  1. Principal 提取:默认取 JWT 的 sub Claim 作为 Authentication.getPrincipal()
  2. 权限提取:默认从 scope 或 scp Claim 中提取权限
  3. 前缀处理:默认给每个提取的权限添加 SCOPE_ 前缀

例如 JWT Payload 为:

{
  "sub": "user123",
  "scope": "read write",
  "iss": "auth-server"
}

默认转换后的权限为:SCOPE_read、SCOPE_write

自定义扩展点

组件方法用途
JwtAuthenticationConvertersetJwtGrantedAuthoritiesConverter()替换权限提取逻辑
JwtAuthenticationConvertersetPrincipalClaimName()指定 Principal 来源 Claim
JwtGrantedAuthoritiesConvertersetAuthoritiesClaimName()指定权限来源 Claim
JwtGrantedAuthoritiesConvertersetAuthorityPrefix()修改权限前缀

示例一:默认 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 从 scope Claim 中提取 "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") 告诉提取器从 roles Claim 读取权限
  • 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"(字符串)期望数组提取提取器可能无法正确解析,权限为空

排查步骤:

  1. 打印实际转换后的权限:
@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;
}
  1. 确认 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
上一页
JWT 认证