如何在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()"); } }
验证效果
用
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。解码返回的
access_token(可以用本地代码或在线工具解码),就能看到JWT payload里已经包含了我们添加的自定义字段,和原有的user_name、scope等字段共存。用其他授权类型(比如
client_credentials)请求token,解码后不会有这些自定义字段,完全符合需求。
内容的提问来源于stack exchange,提问作者Eniss

