You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot OAuth2集成Okta场景下如何为Jwt添加可校验自定义声明

结论:可以实现,但首先要明确核心限制

JWT的签名由授权服务器(Okta)的私钥生成,公钥公开用于校验,你拿到已签发的JWT后如果直接修改声明内容,无法用Okta的私钥重新签名,修改后的令牌会直接验签失败,这也是TokenEnhancer、TokenConverter这类组件不适用的原因——它们都是授权服务器签发令牌时用于增强声明的组件,无法处理已签发完成的令牌。

下面是两种可行实现方案:


方案1:业务侧自行签发嵌套令牌(推荐,侵入性最低)

思路是不修改原Okta签发的JWT,将原令牌和自定义声明封装后用业务侧自己持有的密钥签发新的JWT返回给前端,资源服务器侧先校验外层业务令牌,再取出原Okta令牌走原有校验逻辑即可。

1. 新增JJWT依赖(用于自签发JWT)

如果项目未引入JWT工具包,先添加依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

2. 实现添加自定义声明的接口

@GetMapping("/addIdUser")
public ResponseEntity<String> addUser(@AuthenticationPrincipal Jwt originalJwt) {
    // 定义需要添加的自定义声明
    Map<String, Object> extraClaims = new HashMap<>();
    extraClaims.put("custom_user_id", "你的业务自定义值");
    extraClaims.put("biz_scene", "当前业务场景标识");
    // 存储原Okta令牌的原始值
    extraClaims.put("original_okta_token", originalJwt.getTokenValue());

    // 用业务侧自己的密钥签发新JWT,过期时间和原令牌保持一致即可
    Key secretKey = Keys.hmacShaKeyFor("你自己设置的至少32位安全密钥".getBytes(StandardCharsets.UTF_8));
    String newToken = Jwts.builder()
            .setClaims(extraClaims)
            .setExpiration(originalJwt.getExpiresAt())
            .signWith(secretKey, SignatureAlgorithm.HS256)
            .compact();

    return new ResponseEntity<>(newToken, HttpStatus.OK);
}

3. 资源服务器新增自定义过滤器处理校验逻辑

在原有OAuth2校验过滤器之前新增一层过滤器,先解析外层自签发令牌,再将原Okta令牌透传给后续的Spring Security校验流程:

@Configuration
@EnableWebSecurity
public class ResourceServerConfig {

    @Value("${biz.jwt.secret}")
    private String selfJwtSecret;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                // 自定义过滤器放在Bearer令牌校验过滤器之前
                .addFilterBefore(new CustomJwtExtractFilter(selfJwtSecret), BearerTokenAuthenticationFilter.class)
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }

    public static class CustomJwtExtractFilter extends OncePerRequestFilter {
        private final String secret;

        public CustomJwtExtractFilter(String secret) {
            this.secret = secret;
        }

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String bearerToken = resolveToken(request);
            if (bearerToken == null) {
                filterChain.doFilter(request, response);
                return;
            }
            try {
                // 校验外层自签发JWT的合法性
                Jws<Claims> jws = Jwts.parserBuilder()
                        .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
                        .build()
                        .parseClaimsJws(bearerToken);
                // 取出自定义声明存入请求属性,业务代码可直接读取
                request.setAttribute("custom_claims", jws.getBody());
                // 替换请求头的Authorization为原Okta令牌,供后续OAuth2校验
                HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
                    @Override
                    public String getHeader(String name) {
                        if (HttpHeaders.AUTHORIZATION.equals(name)) {
                            return "Bearer " + jws.getBody().get("original_okta_token");
                        }
                        return super.getHeader(name);
                    }
                };
                filterChain.doFilter(wrapper, response);
            } catch (JwtException e) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("非法令牌");
            }
        }

        private String resolveToken(HttpServletRequest request) {
            String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION);
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
                return bearerToken.substring(7);
            }
            return null;
        }
    }
}

4. 业务代码读取自定义声明

@GetMapping("/biz/test")
public String testBiz(HttpServletRequest request) {
    Claims customClaims = (Claims) request.getAttribute("custom_claims");
    String customUserId = (String) customClaims.get("custom_user_id");
    return "自定义用户ID:" + customUserId;
}

方案2:调用Okta令牌交换接口

如果不希望自行维护令牌签名逻辑,可以调用Okta的令牌交换接口,请求Okta为你签发包含自定义声明的新令牌,所有令牌均由Okta签名,无需修改原有资源服务器的校验逻辑。
操作步骤为:

  1. 在Okta后台配置令牌交换策略,开放自定义声明映射规则
  2. 业务代码中调用Okta的/token接口,grant_type指定为urn:ietf:params:oauth:grant-type:token-exchange,传入原有令牌和需要添加的自定义声明参数
  3. 拿到Okta返回的新令牌直接返回给前端即可

内容的提问来源于stack exchange,提问作者CoderJammer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 04:45:07