【问题标题】:How can OAuth2 resource server use endpoint to access public key when I add custom jwt details to the authorization server?当我向授权服务器添加自定义 jwt 详细信息时,OAuth2 资源服务器如何使用端点访问公钥?
【发布时间】:2023-01-01 12:36:20
【问题描述】:

我自定义授权服务器以将自定义详细信息添加到 JSON Web 令牌,并希望资源服务器应使用端点访问授权服务器上的验证程序公钥。但是 OAuth2AuthenticationDetails.getDecodedDetails() 返回 null。 我的代码结构如下所示:

自定义令牌增强器类:

public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oauth2AccessToken,
            OAuth2Authentication oauth2Authentication) {
      var customToken = new DefaultOAuth2AccessToken(oauth2AccessToken);
   Map<String, Object> customInfo =  Map.of("generatedIn", "Year "+LocalDateTime.now().getYear());

customToken.setAdditionalInformation(customInfo);

return customToken;
}
}

授权服务器类:

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
@Value("${password}")
    private String password;
    
@Value("${privateKey}")
    private String privateKey;
    
@Value("${alias}")
    private String alias;
    
//autowire the authentication manager here
    @Autowired
    private AuthenticationManager authenticationManager;
//provide clients' details
@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
               .withClient("client")
               .secret("secret")
               .authorizedGrantTypes("password", "refresh_token")
               .scopes("read")
               .and()
               .withClient("resourceserver")
               .secret("resourceserversecret");
    }

 @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
     //Define a token enhancer chain here
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
         
        //Add the two token enhancer objects to a list
        var tokenEnhancers =
                List.of(new CustomTokenEnhancer(), jwtAccessTokenConverter());
        
        //Add the token enhancer list to the chain of token enhancer
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
        
endpoints.authenticationManager(authenticationManager)
              .tokenStore(tokenStore())
              .tokenEnhancer(tokenEnhancerChain);
     
     }
@Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            
         /*
          * Configures the authorization server to expose and endpoint for 
           * public key for any authenticated 
          * request with valid client credentials
          */
         security.tokenKeyAccess("isAuthenticated()");
         
        }
       @Bean
        public TokenStore tokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
        @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        
        var converter = new JwtAccessTokenConverter();
        
        
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(
                        new ClassPathResource(privateKey),
                        password.toCharArray()
                        );
        
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair(alias));
        return converter;
    }
}

应用程序.properties 文件:

password = somepassword
privateKey =key.jks
alias = somekey

资源服务器:


@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}

应用程序.properties 文件:

server.port = 9090
security.oauth2.resource.jwt.key-uri=http://localhost:8080/oauth/token_key

security.oauth2.client.client-id=resourceserver
security.oauth2.client.client-secret=resourceserversecret

资源服务器上的受保护端点:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(OAuth2Authentication authentication) {
        OAuth2AuthenticationDetails details =
                (OAuth2AuthenticationDetails) authentication.getDetails();

        return details.getDecodedDetails().toString();
    }
}

当我发出 curl 请求时,调用 details.getDecodedDetails().toString() 的结果将 null 打印到控制台:curl -H "Authorization:Bearer e1yhrjkkkfk....." http://localhost:9090/hello

但是,如果我像这样实现资源服务器,代码将按预期运行:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{

    
    @Value("${publicKey}") //from the properties file
    private String publicKey;
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        
    }

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

    
    
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    
    var converter = new OtherAccessTokenConverter(); //Handles the claims in our custom token. 
    converter.setVerifierKey(publicKey);
    
    return converter;
}

    
}

OtherAccessTokenConverter类:

public class OtherAccessTokenConverter extends JwtAccessTokenConverter {

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
//Get the initial authenticated object
        var  authentication = super.extractAuthentication(map);
        
        //Add the custom details to the authentication object
        authentication.setDetails(map);
        
        //Return the authentication object
        return authentication;
        
    }

但我从来不想在资源服务器上拥有公共验证器密钥,而是通过端点访问。我该怎么做?

【问题讨论】:

    标签: java spring oauth-2.0


    【解决方案1】:
    • 我根据您的示例假设您已经拥有 spring-boot-starter-oauth2-resource-server 依赖项:
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    

    您使用的是什么版本的 spring boot + spring security?我问的原因是因为一旦你有了上面的依赖,你就不需要明确地包含@EnableResourceServer

    • 然后您所要做的就是添加属性,如下所示:
    spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://openam.localtest.me:8080/openam/oauth2/realms/subrealm/connect/jwk_uri
    

    注意:我还注意到您在属性文件中缺少 spring. 前缀?

    这是您唯一需要做的两件事。无需手动覆盖 Spring Boot 的 startup 模块提供的默认值。

    然后会发生的是,一旦您的 Web / REST 端点收到带有 Bearer 令牌的 HTTP Authorization 标头.........不记名令牌是签名的 JWT,然后资源服务器将:

    • 查询spring.security.oauth2.resourceserver.jwt.jwk-set-uri属性指定的URL。
    • 从该 URL 中的密钥集中,资源服务器随后将找到与签名的 JWT 不记名令牌中的 kid 具有相同 kid 的公钥。
    • 一旦找到,资源服务器就会验证已签名的 JWT 不记名令牌的签名,如果无法验证已签名的 JWT,则返回 HTTP 401,如果成功则返回 HTTP 200(假设您没有其他执法到位)。

    我有点在同一个旅程中,我必须以某种方式使公钥可见,以某种方式列在资源服务器的 application.properties 文件中定义的 URL 中,指向 ForgeRocks AM 的 connect/jwk_uri ...但是我添加的公钥JWK套装当从浏览器/curl 查询时,ForgeRock AM 受信任的 JWT 发行人的身份未出现在 connect/jwk_uri URL 中。

    似乎 Spring Security 的资源服务器支持 JWT 不记名令牌授权,如 Spring Security 文档 URL 中突出显示的那样:

    https://docs.spring.io/spring-security/reference/5.7/servlet/oauth2/resource-server/jwt.html

    ...与 ForgeRock AM 的预期不兼容。

    例如。:

    SpringBoot OAuth2 资源服务器在接收签名的 JWT 作为 Authorization HTTP 标头中的承载时期望以下内容:

    • 客户端创建并签署 JWT,并将签署的 JWT 作为 Authorization HTTP 标头请求中的不记名令牌发送到授权类型为 urn:ietf:params:oauth:grant-type:jwt-bearer 的资源服务器。
    • Spring 的 OAuth2 资源服务器发现不记名令牌是签名的 JWT,因此要么从 spring.security.oauth2.resourceserver.jwt.jwk-set-uri 属性中查找匹配的公钥,要么从 spring.security.oauth2.resourceserver.jwt.public-key-location 本地加载它们(这个应该是 PEM 格式) .
    • Spring 的 OAuth2 资源服务器使用从上一步找到的匹配公钥验证签名的 JWT 不记名令牌。
    • 然后 Spring 的 OAuth2 资源服务器允许 HTTP 请求通过。否则,返回 HTTP 401。

    而 ForgeRock AM 的模型需要不同的流程(此处记录:https://backstage.forgerock.com/docs/am/7.2/oauth2-guide/oauth2-jwt-bearer-grant.html

    • 预计资源服务器将设置为不透明的不记名令牌,而不是 JWT 不记名令牌,如此处定义:

    https://docs.spring.io/spring-security/reference/5.7/servlet/oauth2/resource-server/opaque-token.html

    因此,您在资源服务器的application.properties 中更改以下内容,如下所示:

    spring.security.oauth2.resourceserver.opaque-token.introspection-uri=http://openam.localtest.me:8080/openam/oauth2/realms/subrealm/introspect
    spring.security.oauth2.resourceserver.opaque-token.client-id=someclient
    spring.security.oauth2.resourceserver.opaque-token.client-secret=somesecret
    

    ...并删除/注释掉与 jwk / jwt 相关的其他属性。 (例如注释掉spring.security.oauth2.resourceserver.jwt.jwk-set-uri

    • 客户端创建并签署 JWT,并将签署的 JWT 作为 Authorization HTTP 标头请求中的不记名令牌发送到授权类型为 urn:ietf:params:oauth:grant-type:jwt-bearer 的 ForgeRock 授权服务器。
    • AM 服务器然后验证签名的 JWT 并生成访问令牌(Spring 词汇表中的不透明令牌)并将其返回给客户端。
    • 然后客户端发送并使用这个不透明的访问令牌作为 Authorization HTTP 标头请求中的不记名令牌到资源服务器(例如 Spring 的 OAuth2 资源服务器)。
    • 因为它是一个不透明的令牌,所以 Spring'2 OAuth2 资源服务器然后调用内省 URI (spring.security.oauth2.resourceserver.opaque-token.introspection-uri),这是 AM 服务器公开的端点,用于验证访问令牌。
    • 根据调用结果,资源服务器拒绝(使用 HTTP 401)或接受来自客户端的 HTTP 调用。

    因此(无法将我添加到 ForgeRock AM 的 connect/jwk_uri 的 HTTP 输出中的公钥包含在内,我现在希望遵循 ForgeRock AM 记录的授权流程。


    此外,文档位于:

    https://docs.spring.io/spring-security/reference/5.7/servlet/oauth2/resource-server/jwt.html

    .. 表示您需要明确包含 spring-security-oauth2-resource-serverspring-security-oauth2-jose 以支持签名的 JWT。但是 Maven 依赖树表明它被自动包含为传递依赖:

    $ mvn dependency:tree -Dincludes=org.springframework.security:spring-security-oauth2-jose:jar
    [INFO] Scanning for projects...
    [INFO] 
    [INFO] ---------------< org.example.oauth2-resource-server:jwt >---------------
    [INFO] Building oauth2-resource-server 0.0.1-SNAPSHOT
    [INFO] --------------------------------[ jar ]---------------------------------
    [INFO] 
    [INFO] --- maven-dependency-plugin:3.3.0:tree (default-cli) @ jwt ---
    [INFO] org.example.oauth2-resource-server:jwt:jar:0.0.1-SNAPSHOT
    [INFO] - org.springframework.boot:spring-boot-starter-oauth2-resource-server:jar:2.7.7:compile
    [INFO]    - org.springframework.security:spring-security-oauth2-jose:jar:5.7.6:compile
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  0.629 s
    [INFO] Finished at: 2023-01-01T14:33:49+11:00
    [INFO] ------------------------------------------------------------------------
    

    【讨论】:

      猜你喜欢
      • 2019-02-22
      • 2022-11-04
      • 2021-10-29
      • 2016-05-05
      • 2019-10-27
      • 2016-05-21
      • 2014-07-09
      • 2018-08-29
      • 2017-08-26
      相关资源
      最近更新 更多