oauth2.0 如何在同一个Sping Boot 应用中使用多个“JWK Set Uri”值?

alen0pnh  于 2023-10-15  发布在  其他
关注(0)|答案(3)|浏览(92)

我需要使用两个不同的授权服务器(两个Okta示例)来验证来自单个Sping Boot 应用程序(后端REST API层)中的两个不同Web应用程序的身份验证令牌。
目前,我有一个资源服务器使用以下配置:

@Configuration
@EnableWebSecurity
public class ResourceServerSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception{
    http
      .authorizeRequests().antMatchers("/public/**").permitAll()
      .anyRequest().authenticated()
      .and()
      .oauth2ResourceServer().jwt();
  }
}
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-X.okta.com/oauth2/default
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://dev-X.okta.com/oauth2/default/v1/keys

并在我的Sping Boot 应用程序(版本2.2.4.RELEASE)中使用依赖项spring-security-oauth2-resource-serverspring-security-oauth2-jose
我想要进入的结束状态是,根据请求中设置的自定义HTTP头,我想要选择Sping Boot 应用程序使用哪个Okta示例来解码和验证JWT令牌。
理想情况下,我的配置文件中应该有两个属性,如下所示:

jwkSetUri.X=https://dev-X.okta.com/oauth2/default/v1/keys
jwtIssuerUri.X=https://dev-X.okta.com/oauth2/default

jwkSetUri.Y=https://dev-Y.okta.com/oauth2/default/v1/keys
jwtIssuerUri.Y=https://dev-Y.okta.com/oauth2/default

我应该能够使用RequestHeaderRequestMatcher来匹配安全配置中的头值。我无法解决的问题是如何使用两个不同的oauth2ResourceServer示例来进行安全配置。

zqry0prt

zqry0prt1#

与 Spring Boot ,这是不可能做的开箱即用,现在。Spring Security 5.3提供了这样做的功能(spring Boot 2.2.6仍然不支持Spring Security 5.3)。请参见以下问题:
https://github.com/spring-projects/spring-security/issues/7857
https://github.com/spring-projects/spring-security/pull/7887
可以通过以下链接手动配置资源服务器以使用多个身份提供程序。所提供的链接主要用于spring Boot webflux开发。有关基本的Sping Boot Web开发,请参阅此视频:
https://www.youtube.com/watch?v=ke13w8nab-k

kb5ga3dv

kb5ga3dv2#

从Spring security 5.3+开始,可以使用JwtIssuerAuthenticationManagerResolver对象
在扩展WebSecurityConfigurerAdapter的配置类中添加configure(HttpSecurity http)

JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
            "http://localhost:8080/auth/realms/SpringBootKeyClock",
            "https://accounts.google.com/o/oauth2/auth",
            "https://<subdomain>.okta.com/oauth2/default"
    );

http.cors()
            .and()
            .authorizeRequests()
            .antMatchers(HttpMethod.GET, "/user/info", "/api/foos/**")
            .hasAnyAuthority("SCOPE_email")
            .antMatchers(HttpMethod.POST, "/api/foos")
            .hasAuthority("SCOPE_profile")
            .anyRequest()
            .authenticated()
            .and()
            .oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
xxls0lw8

xxls0lw83#

第一步:创建自定义AuthenticationManagerResolver

@Component
    public class TenantAuthenticationManagerResolver implements
        AuthenticationManagerResolver<HttpServletRequest> {private final Map<String, String> tenants;
      private final Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
    
      private final BearerTokenResolver resolver = new DefaultBearerTokenResolver();
    
      public TenantAuthenticationManagerResolver() {
        this.tenants = new HashMap<>();
      }
    
      public TenantAuthenticationManagerResolver(String tenantIds, String jwkUris) {
        List<String> tenantList = Arrays.asList(tenantIds.split(","));
        List<String> issuerList = Arrays.asList(jwkUris.split(","));
    
        this.tenants = IntStream.range(0, Math.min(tenantList.size(), issuerList.size()))
            .boxed()
            .collect(Collectors.toMap(tenantList::get, issuerList::get));
      }
    
      @Override
      public AuthenticationManager resolve(HttpServletRequest request) {
        return this.authenticationManagers.computeIfAbsent(toTenant(request), this::fromTenant);
      }
    
      private String toTenant(HttpServletRequest request) {
        String token = this.resolver.resolve(request);
        try {
          return (String) JWTParser.parse(token).getJWTClaimsSet().getClaim(TENANT_ID);
        } catch (Exception e) {
          throw new IllegalArgumentException(e);
        }
      }
    
      private AuthenticationManager fromTenant(String tenant) {
        return Optional.ofNullable(this.tenants.get(tenant))
            .map(issuer -> NimbusJwtDecoder.withJwkSetUri(issuer).build())
            .map(JwtAuthenticationProvider::new)
            .orElseThrow(() -> new IllegalArgumentException("unknown tenant"))::authenticate;
      }
    
    }

第二步:在安全配置中执行以下操作

@Value("${issuer.jwkUris}")
  private String jwkUris;

  @Value("${tenantId.tenants}")
  private String tenantIds;

  @Bean
  @Qualifier("tenantIds")
  public String tenantIds() {
    return tenantIds;
  }

  @Bean
  @Qualifier("jwkUris")
  public String jwkUris() {
    return jwkUris;
  }

  @Bean
  public TenantAuthenticationManagerResolver authenticationManagerResolver() {
    return new TenantAuthenticationManagerResolver(tenantIds(), jwkUris());
  }

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
        .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(
            restAuthenticationEntryPoint()))
        .oauth2ResourceServer(
            oauth2ResourceServer -> oauth2ResourceServer.authenticationManagerResolver(authenticationManagerResolver()));
    return http.build();
  }

相关问题