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

如何在grant_type=password生成的JWT access_token中新增字段

这个问题我之前也碰到过——要给password授权类型生成的JWT偷偷加字段,还不能让这些字段出现在API响应里,只留在token内部。其实核心就是区分JWT payload和OAuth2响应体的内容,别把自定义字段加到响应体的额外信息里就行。下面是具体的实现方案:

核心思路

默认情况下,如果用普通的TokenEnhancer往OAuth2AccessToken的additionalInformation里加字段,这些字段会同时出现在API响应体和JWT payload中。我们需要只修改JWT的payload,不碰响应体的额外信息,同时只对password授权类型生效。

最直接的方式是自定义JwtAccessTokenConverter(它本身负责把OAuth2令牌转换成JWT),在转换JWT payload的时候判断授权类型,针对性添加字段。

步骤1:自定义JwtAccessTokenConverter

重写convertAccessToken方法,这里可以拿到生成JWT所需的claims集合,我们只在grant_type=password时添加自定义字段:

import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.Map;

public class PasswordGrantJwtConverter extends JwtAccessTokenConverter {

    @Override
    protected Map<String, Object> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
        // 先调用父类方法获取默认的claims(包含user_name、scope、exp等字段)
        Map<String, Object> claims = super.convertAccessToken(token, authentication);
        
        // 从授权请求中获取grant_type参数
        Map<String, String> requestParams = authentication.getOAuth2Request().getRequestParameters();
        String grantType = requestParams.get("grant_type");
        
        // 仅当grant_type为password时,添加自定义字段
        if ("password".equals(grantType)) {
            // 示例:添加自定义字段,这里可以替换成你需要的内容
            claims.put("custom_user_id", authentication.getName());
            claims.put("custom_role", "VIP_USER");
            
            // 如果需要从用户详情里取更复杂的信息,比如用户ID、昵称等:
            // UserDetails user = (UserDetails) authentication.getPrincipal();
            // claims.put("user_nickname", user.getUsername()); // 假设username是昵称
        }
        
        return claims;
    }
}

步骤2:配置授权服务器

在你的AuthorizationServerConfig里,替换默认的JwtAccessTokenConverter为我们自定义的实例,并配置签名密钥(用于JWT签名):

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    // 如果需要从用户详情服务取信息,可以注入UserDetailsService

    public AuthorizationServerConfig(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Bean
    public PasswordGrantJwtConverter jwtAccessTokenConverter() {
        PasswordGrantJwtConverter converter = new PasswordGrantJwtConverter();
        // 设置JWT签名密钥,生产环境建议用非对称密钥(RSA)
        converter.setSigningKey("your-strong-signing-key-here");
        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                // .userDetailsService(userDetailsService) // 如果需要的话注入
                .tokenStore(tokenStore())
                .accessTokenConverter(jwtAccessTokenConverter());
    }

    // 配置客户端详情、安全规则等其他必要配置
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-id")
                .secret("{noop}client-secret")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("trust");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }
}

验证效果

  1. 用password授权类型请求token:

    curl -X POST "http://localhost:8080/oauth/token" \
         -H "Content-Type: application/x-www-form-urlencoded" \
         -d "grant_type=password&username=your-user&password=your-pass&client_id=client-id&client_secret=client-secret"
    

    响应体里只会返回标准字段(access_token、token_type、expires_in等),看不到我们加的custom_user_id或custom_role。

  2. 解码返回的access_token(可以用本地代码或在线工具解码),就能看到JWT payload里已经包含了我们添加的自定义字段,和原有的user_name、scope等字段共存。

  3. 用其他授权类型(比如client_credentials)请求token,解码后不会有这些自定义字段,完全符合需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:30:43