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

如何在Spring Authorization Server中无密码重签带新增声明的JWT

问题根因

原有代码无法生成新令牌的核心原因:

  • JwtAuthenticationProvider的唯一职责是校验传入Bearer Token的有效性、解析令牌内容构建认证对象,本身不承载任何新令牌签发逻辑,传入现有有效JWT走该流程只会返回解析后的原令牌认证信息,自然不会产出新令牌。
  • Spring Authorization Server(以下简称SAS)的令牌签发逻辑默认和授权授予(Authorization Grant)流程绑定,没有直接暴露"传入旧Token直接签发新Token"的开箱即用接口,需要手动复用官方核心服务组件实现自定义逻辑。
实现方案(无用户交互、无需Refresh Token)

核心思路是复用SAS内置的OAuth2TokenGenerator、OAuth2AuthorizationService等核心组件,基于当前已认证的用户上下文直接生成带新声明的JWT,全程不需要用户交互,也不依赖Refresh Token机制。

第一步:配置自定义声明注入逻辑

实现OAuth2TokenCustomizer<JwtEncodingContext>,仅在令牌重签场景下注入你需要的声明集合B,避免影响正常登录等其他流程的令牌生成:

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
    return context -> {
        // 匹配自定义的令牌重签授权类型
        if ("urn:ietf:params:oauth:grant-type:token-renew".equals(context.getAuthorizationGrantType().getValue())) {
            // 此处写入声明集合B,可从当前认证上下文、业务数据库查询对应值写入
            context.getClaims().claim("customField1", "value1");
            context.getClaims().claim("customField2", "value2");
        }
    };
}

第二步:编写令牌重签接口

注入SAS核心组件,构造合法的令牌生成上下文,直接调用令牌生成器产出新JWT,同时将新令牌的授权信息持久化到SAS的授权存储中,和官方原生流程保持一致:

@RestController
public class TokenRenewController {
    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
    private final OAuth2AuthorizationService authorizationService;
    private final RegisteredClientRepository registeredClientRepository;

    // 构造注入SAS核心Bean
    public TokenRenewController(OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
                                OAuth2AuthorizationService authorizationService,
                                RegisteredClientRepository registeredClientRepository) {
        this.tokenGenerator = tokenGenerator;
        this.authorizationService = authorizationService;
        this.registeredClientRepository = registeredClientRepository;
    }

    @PostMapping("/renew")
    public OAuth2AccessTokenResponse renewToken() {
        // 从SecurityContext获取当前已认证的用户身份
        Authentication currentUserAuth = SecurityContextHolder.getContext().getAuthentication();
        // 替换为你当前公开客户端在SAS中注册的clientId
        String clientId = "your-public-client-id";
        RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId);

        // 定义自定义的令牌重签授权类型
        AuthorizationGrantType renewGrantType = new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:token-renew");
        Set<String> authorizedScopes = new HashSet<>(registeredClient.getScopes());

        // 构造令牌生成上下文
        OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
                .registeredClient(registeredClient)
                .principal(currentUserAuth)
                .authorizationService(authorizationService)
                .authorizedScopes(authorizedScopes)
                .tokenType(OAuth2TokenType.ACCESS_TOKEN)
                .authorizationGrantType(renewGrantType)
                .build();

        // 生成带新声明的JWT
        OAuth2Token generatedToken = tokenGenerator.generate(tokenContext);
        if (generatedToken == null) {
            throw new IllegalStateException("新令牌生成失败");
        }
        Jwt newJwt = (Jwt) generatedToken;

        // 将新令牌对应的授权信息存入SAS授权存储,保证后续校验正常
        OAuth2Authorization newAuthorization = OAuth2Authorization.withRegisteredClient(registeredClient)
                .principalName(currentUserAuth.getName())
                .authorizationGrantType(renewGrantType)
                .authorizedScopes(authorizedScopes)
                .token(newJwt, metadata -> 
                        metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, newJwt.getClaims())
                )
                .build();
        authorizationService.save(newAuthorization);

        // 按标准OAuth2令牌格式返回
        return OAuth2AccessTokenResponse.withToken(newJwt.getTokenValue())
                .tokenType(OAuth2AccessToken.TokenType.BEARER)
                .expiresIn(newJwt.getExpiresAt().getEpochSecond() - Instant.now().getEpochSecond())
                .scopes(authorizedScopes)
                .build();
    }
}

第三步:配置接口访问规则

将重签接口加入SAS安全匹配路径,要求必须携带有效JWT才能访问,避免匿名调用:

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfigurer sasConfigurer = new OAuth2AuthorizationServerConfigurer();
    http.securityMatcher(endpoints -> {
                List<RequestMatcher> matchers = new ArrayList<>();
                matchers.add(new AntPathRequestMatcher("/renew", HttpMethod.POST.name()));
                matchers.addAll(sasConfigurer.getEndpointsMatcher().getMatchers());
                return new OrRequestMatcher(matchers).matches(endpoints);
            })
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/renew")).authenticated()
                    .anyRequest().authenticated()
            )
            // 开启JWT资源服务器校验,保证接口必须携带有效JWT才能访问
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    // 原有SAS配置保持不变即可
    http.apply(sasConfigurer);
    return http.build();
}
注意事项
  • 若需要让旧令牌在新令牌签发后立即失效,直接调用authorizationService.remove()删除旧JWT对应的OAuth2Authorization记录即可,不需要额外实现黑名单逻辑,SAS本身会基于持久化的授权记录校验令牌有效性。
  • 上述实现完全不涉及Refresh Token签发逻辑,适配公开客户端的权限限制,不需要修改原有客户端的配置属性。
  • 自定义授权类型标识可自行命名,只要不与SAS内置的authorization_code、client_credentials、refresh_token等内置类型冲突即可,不会影响原有认证流程。
  • 新JWT默认沿用对应RegisteredClient配置的Access Token有效期,若需要自定义重签令牌的TTL,可在构造DefaultOAuth2TokenContext时通过tokenSettings传入自定义有效期参数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:15:29