OAuth2 Spring Security 将多个 API 公开为多个资源服务器

问题描述 投票:0回答:1

我正在使用 Spring Security OAuth2 支持将多个 API 作为资源服务器从单个 Web 应用程序公开。我们希望每个 API 都需要不同的“受众”,以便不同的程序可以访问不同的 API。我为每个 API 设置单独的 SecurityFilterChains(相同的颁发者 URI,但不同的受众)。通过这种配置,每个安全过滤器链都有不同的承载令牌过滤器。我看到的问题是,任何 OAuth2 传入请求都会被安全过滤器链之一拾取(基于顺序),并且由于正确的相应身份验证类型,它会尝试处理。但是,如果受众与该 API 的值不匹配,则会引发错误,并且不会尝试其他 API。看来承载令牌过滤器位于请求匹配器之前,因此它正在处理与另一个 API URL 匹配的消息。

经过一些研究,似乎需要 AuthenticationManagerResolver 的替代实现(而不是 JwtIssuerAuthenticationManagerResolver)来查找受众匹配。或者,我想我们可以考虑在承载令牌过滤器之前放置一个请求匹配。

有什么想法或建议吗?

spring-oauth2
1个回答
0
投票

我认为你最初的想法是使用一个

SecurityFilterChain
bean par“API”是正确的。也许您只是缺少每个
securityMatcher
(但是
@Order
中的最后一个,因此它充当默认值)。

您很可能可以基于路径前缀定义安全匹配器。也许,如果所有访问令牌都是 JWT,您可以尝试匹配

aud
声明上的请求而不是请求路径,但第一种方法肯定更容易。

这是具有两种不同方法的示例:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain
        audxFilterChain(HttpSecurity http, @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") URI issuer, @Value("${audx}") String audx)
                throws Exception {
    http.securityMatcher("/api/x/**");

    http.oauth2ResourceServer(oauth2 -> {
        oauth2.jwt(jwt -> {
            final var issValidator = JwtValidators.createDefaultWithIssuer(issuer.toString());
            final var audValidator = new JwtClaimValidator<List<String>>(JwtClaimNames.AUD, (aud) -> aud != null && aud.contains(audx));
            final var validator = new DelegatingOAuth2TokenValidator<>(List.of(issValidator, audValidator));

            final var decoder = NimbusJwtDecoder.withIssuerLocation(issuer.toString()).build();
            decoder.setJwtValidator(validator);

            jwt.decoder(decoder);
        });
    });

    http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    http.csrf(csrf -> csrf.disable());

    return http.build();
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
SecurityFilterChain
        audyFilterChain(HttpSecurity http, @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") URI issuer, @Value("${audy}") String audy)
                throws Exception {
    http.securityMatcher((HttpServletRequest request) -> {
        return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
                .filter(StringUtils::hasText)
                .filter(auth -> auth.toLowerCase().startsWith("bearer "))
                .map(auth -> auth.substring(7))
                .map(bearer -> {
                    try {
                        final var jwt = JWTParser.parse(bearer);
                        return jwt.getJWTClaimsSet().getAudience() != null && jwt.getJWTClaimsSet().getAudience().contains(audy);
                    } catch (ParseException e) {
                        return false;
                    }
                }).orElse(false);
    });

    http.oauth2ResourceServer(oauth2 -> {
        // audience is already validated in the matcher
        oauth2.jwt(jwt -> {
        });
    });

    http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    http.csrf(csrf -> csrf.disable());

    return http.build();
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
SecurityFilterChain defaultFilterChain(HttpSecurity http, @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") URI issuer) throws Exception {
    // it's generally a good idea to define a default filter-chain for requests that were matched 
    // by none of the security matchers from higher @Order filter-chains
    http.authorizeHttpRequests(requests -> requests.anyRequest().denyAll());

    http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    http.csrf(csrf -> csrf.disable());

    return http.build();
}
© www.soinside.com 2019 - 2024. All rights reserved.