Spring Security WebFlux:无法从Azure B2C对JWT令牌进行身份验证

wyyhbhjk  于 2023-01-09  发布在  Spring
关注(0)|答案(2)|浏览(152)

我正在尝试实现将Azure B2C与 Boot 的Webflux Security结合使用的能力,虽然没有官方的库来实现这一点,微软的某个人说Spring Security 5的原生特性可以支持Azure B2C。我一直在关注这个repository(尽管它不是基于webflux的)来了解如何实现这一点。JWT令牌通过应用程序的受众UUID进行验证。
一旦我尝试实际向请求提供JWT标记,就会收到一个HTTP 401错误,说明为Authentication failed: Failed to validate the token
问题是,在示例存储库中,他们使用端点https://login.microsoftonline.com/{tenantId}/v2.0作为发行者url。
另一方面,从B2C返回的JWT令牌具有发行者https://{tenantName}.b2clogin.com/{tenantId}/v2.0/
如果使用发行者https://{tenantName}.b2clogin.com/{tenantId}/v2.0/,JWT解码器将无法找到配置。
所以现在我觉得发行者URL实际上是不一致的,这阻止了Webflux实际上能够执行身份验证。
这是我的安全密码。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
public class SecurityConfiguration {

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    String issuerUri;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)throws Exception {
        return http.cors().and().csrf().disable()
                .authorizeExchange()
                .anyExchange()
                .authenticated()
                .and().oauth2ResourceServer().jwt().and().and().build();

    }

    @Bean
    ReactiveJwtDecoder jwtDecoder() {
        NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder) ReactiveJwtDecoders.fromIssuerLocation(issuerUri);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator();
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }
}

观众验证器。

import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;

public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
    OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);

    public OAuth2TokenValidatorResult validate(Jwt jwt) {
        if (jwt.getAudience().contains("messaging")) {
            return OAuth2TokenValidatorResult.success();
        } else {
            return OAuth2TokenValidatorResult.failure(error);
        }
    }
}

application.yml

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys 
          issuer-uri: https://login.microsoftonline.com/{tenantId}/v2.0
          audience: {audience-id}

所以,我有三个问题:
1.发布者URL到底是怎么回事?
1.如何允许Spring Security 5React式与Azure B2C一起工作?
1.我注意到JWT解码器在启动时只被调用一次,在端点被调用时它不会被调用,为什么会这样呢?

67up9zun

67up9zun1#

颁发者URL https://login.microsoftonline.com/{tenantId}/v2.0用于Azure AD。
由于Azure B2C依赖于定义的配置文件,因此您必须使用https://{tenantName}.b2clogin.com/tfp/{tenantId}/{profileName}/v2.0/作为颁发者URL。
虽然https://{tenantName}.b2clogin.com/{tenantId}/{profileName}/v2.0/也是有效的颁发者,但Spring Security将报告颁发者URL不一致,因为颁发者实际上是https://{tenantName}.b2clogin.com/{tenantId}/v2.0/
Azure B2C似乎没有通用颁发者,也没有包含所有密钥的JWK列表。

jq6vz3qz

jq6vz3qz2#

基本实施

默认情况下,如果您想要完整的标准设置,并从资源YML配置文件中提取值,那么现在就可以使用这些方法了。下面是用于绝对准系统实现的securityWebFilterChain,它包括执行所有三项检查(颁发者、密钥验证和令牌过期):

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {

        http
                .cors().and()
                .csrf().disable()
                .authorizeExchange(exchanges -> exchanges
                        .anyExchange().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(withDefaults())
                );
        return http.build();
    }
}

执行此实现意味着您 * 必须 * 拥有这些默认资源位置的正确的application.yml文件条目。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys 
          issuer-uri: https://login.microsoftonline.com/{tenantId}/v2.0

使用代理设置实现

只要你不在公司防火墙后面,上述基本设置的一切都将工作得很好。如果你在防火墙后面,你必须找到一种方法来将代理设置包含在实现中。令所有人懊恼的是,由于某种原因,Spring Security 5的这个特定部分的默认实现不支持与代理相关的JAVA_OPT设置。要解决这个问题,你必须实现你自己的代理覆盖。2是的,我试过覆盖代理bean。3它也没有被接受。4我能让它工作的唯一方法是示例化一个web客户端,同时直接覆盖代理设置。
这意味着必须为oauth2上的JWT方法注入customerDecoder,并在该customer解码器中示例化一个具有自定义客户端连接器定义(该定义显式定义了其代理设置)的web客户端。当然,由于要重载解码器,因此必须重新定义默认情况下通常包含的所有特性(包括前面提到的3种令牌验证类型)。
恶心。
如果你问我的话,很多奇怪的东西应该是标准的。下面是我最后得到的在非防火墙和防火墙情况下都有效的东西:

@Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
                .cors().and()
                .csrf().disable()
                .authorizeExchange(exchanges -> exchanges
                        .anyExchange().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt()
                        .jwtDecoder(this.customDecoder())
                );
        return http.build();
    }

    ReactiveJwtDecoder customDecoder() {
        HttpClient httpClient =
                HttpClient.create()
                        .proxy(proxy -> proxy
                                .type(ProxyProvider.Proxy.HTTP)
                                .host(proxyHost)
                                .port(proxyPort));
        ReactorClientHttpConnector conn = new ReactorClientHttpConnector(httpClient);

        // use a customized webClient with explicit proxy settings for the profile
        final NimbusReactiveJwtDecoder userTokenDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(this.jwkSetUri)
                .webClient(WebClient.builder().clientConnector(conn).build()).build();

        // add both issuer and timestamp validators for JWT token
        OAuth2TokenValidator<Jwt> jwtValidator =
                JwtValidators.createDefaultWithIssuer(issuerUri);
        userTokenDecoder.setJwtValidator(jwtValidator);

        return userTokenDecoder;
    }

相关问题