繁体   English   中英

当我向授权服务器添加自定义 jwt 详细信息时,OAuth2 资源服务器如何使用端点访问公钥?

[英]How can OAuth2 resource server use endpoint to access public key when I add custom jwt details to the authorization server?

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

自定义令牌增强器 class:

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;
}
}

授权服务器 class:

@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;
}

    
}

其他AccessTokenConverter class:

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;
        
    }

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

  • 根据您的示例,我假设您已经具有spring-boot-starter-oauth2-resource-server依赖项:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

您使用的是什么版本的 spring 引导 + spring 安全? 我问的原因是因为一旦你有了上面的依赖,你就不需要显式地包含@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 header ......并且......承载令牌是签名的 JWT,资源服务器将:

  • 查询spring.security.oauth2.resourceserver.jwt.jwk-set-uri属性指定的URL。
  • 从 URL 中的密钥集中,资源服务器将找到与签名的 JWT 不记名令牌中的kid具有相同kid的公钥。
  • 那么理论上,这个 URL 可以在任何地方,它不必由授权服务器提供。
  • 一旦找到,资源服务器就会验证已签名的 JWT 承载令牌的签名,如果无法验证已签名的 JWT,则将 HTTP 401 返回给客户端,如果成功则返回 HTTP 200(假设您没有其他执法措施到位)。

我有点在同一个旅程中,我必须使公钥可见,以某种方式列在资源服务器的application.properties文件中定义的 URL 中,指向 ForgeRock AM 的connect/jwk_uri ...但是我添加的公钥JWK Set of ForgeRock AM's trusted JWT issuer does not appear in the connect/jwk_uri URL when queries from a browser / curl.

似乎 Spring 安全资源服务器支持 JWT 不记名令牌授权,如 Spring 安全文档 URL 中强调的那样:

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

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

例如:

SpringBoot OAuth2 资源服务器 JWT 令牌支持

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

  • 客户端创建并签署一个 JWT,并在Authorization HTTP header 请求中将已签署的 JWT 作为承载令牌发送到资源服务器,授予类型为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's expected flow with JWT profile for OAuth 2.0授权授予

而 ForgeRock AM 的 model 期望不同的流程(此处记录: 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,并在Authorization HTTP header 请求中将已签署的 JWT 作为承载令牌发送到 ForgeRock 授权服务器,授权类型为urn:ietf:params:oauth:grant-type:jwt-bearer 因此预计ForgeRock AM的可信JWT issuer agent已经在上一步配置了signer的公钥JWKS。
  • 然后 AM 服务器验证已签名的 JWT 并生成访问令牌(Spring 词汇表中的不透明令牌)并将其返回给客户端。
  • 客户端然后在Authorization HTTP header 请求中向资源服务器(例如 Spring 的 OAuth2 资源服务器)发送并使用此不透明访问令牌作为承载令牌。
  • 因为它是一个不透明的令牌,Spring'2 OAuth2 资源服务器然后调用内省 URI ( spring.security.oauth2.resourceserver.opaque-token.introspection-uri ),这是 AM 服务器公开的端点,用于验证访问令牌.
  • 根据调用结果,资源服务器拒绝(使用 HTTP 401)或接受来自客户端的 HTTP 调用。

因此(无法包含我添加到 ForgeRock AM 的connect/jwk_uri的 HTTP output 中的公钥,我现在希望遵循 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] ------------------------------------------------------------------------

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM