WebFlux:无法验证来自 Azure B2C 的 JWT 令牌
WebFlux: Can't authenticate JWT token from Azure B2C
我正在尝试实现将 Azure B2C 与 Spring Boot 的 Webflux Security 结合使用的能力。
虽然没有官方图书馆可以实际执行此操作,但 someone at Microsoft that Spring Security 5's native features could support Azure B2C. I've followed this repository(尽管它不是基于 webflux 的)表示要了解如何实现这一点。 JWT 令牌通过应用程序的受众 UUID 进行验证。
一旦我尝试实际向请求提供 JWT 令牌,我收到 HTTP 401 错误,指出 Authentication failed: Failed to validate the token
。
事实是,在示例存储库中,他们正在为发行者 url 使用端点 https://login.microsoftonline.com/{tenantId}/v2.0
。
另一方面,从 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}
所以,我有三个问题:
- 发行人到底怎么了URL?
- 如何让 Spring Security 5 reactive 与 Azure B2C 一起工作?
- 我注意到 JWT 解码器在启动时只被调用一次。调用端点时不会调用它。为什么会这样?
颁发者 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 安全会抱怨不一致的颁发者 URL 因为颁发者实际上是 https://{tenantName}.b2clogin.com/{tenantId}/v2.0/
.
Azure B2C 似乎没有通用发行者,也没有包含所有密钥的 JWK 列表。
基本实施
默认情况下,如果您想要完整的标准设置并从资源 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 设置。要解决这个问题,您必须实现自己的代理覆盖。是的,我尝试覆盖代理 bean。它也没有受到尊重。我能让它工作的唯一方法是实例化一个 Web 客户端,同时直接覆盖代理设置。
这一切都意味着必须为 oauth2 上的 JWT 方法注入一个 customerDecoder,并在该客户解码器内部实例化一个 webclient,该 webclient 具有明确定义其代理设置的自定义客户端连接器定义。当然,由于你重载了解码器,你必须重新定义它通常默认包含的所有功能(包括上述3种类型的令牌验证)。
恶心。
如果你问我,很多奇怪的事情应该是标准的。这是我最终得到的在 non-firewall 和防火墙情况下都有效的结果:
@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;
}
我正在尝试实现将 Azure B2C 与 Spring Boot 的 Webflux Security 结合使用的能力。 虽然没有官方图书馆可以实际执行此操作,但 someone at Microsoft that Spring Security 5's native features could support Azure B2C. I've followed this repository(尽管它不是基于 webflux 的)表示要了解如何实现这一点。 JWT 令牌通过应用程序的受众 UUID 进行验证。
一旦我尝试实际向请求提供 JWT 令牌,我收到 HTTP 401 错误,指出 Authentication failed: Failed to validate the token
。
事实是,在示例存储库中,他们正在为发行者 url 使用端点 https://login.microsoftonline.com/{tenantId}/v2.0
。
另一方面,从 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}
所以,我有三个问题:
- 发行人到底怎么了URL?
- 如何让 Spring Security 5 reactive 与 Azure B2C 一起工作?
- 我注意到 JWT 解码器在启动时只被调用一次。调用端点时不会调用它。为什么会这样?
颁发者 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 安全会抱怨不一致的颁发者 URL 因为颁发者实际上是 https://{tenantName}.b2clogin.com/{tenantId}/v2.0/
.
Azure B2C 似乎没有通用发行者,也没有包含所有密钥的 JWK 列表。
基本实施
默认情况下,如果您想要完整的标准设置并从资源 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 设置。要解决这个问题,您必须实现自己的代理覆盖。是的,我尝试覆盖代理 bean。它也没有受到尊重。我能让它工作的唯一方法是实例化一个 Web 客户端,同时直接覆盖代理设置。
这一切都意味着必须为 oauth2 上的 JWT 方法注入一个 customerDecoder,并在该客户解码器内部实例化一个 webclient,该 webclient 具有明确定义其代理设置的自定义客户端连接器定义。当然,由于你重载了解码器,你必须重新定义它通常默认包含的所有功能(包括上述3种类型的令牌验证)。
恶心。
如果你问我,很多奇怪的事情应该是标准的。这是我最终得到的在 non-firewall 和防火墙情况下都有效的结果:
@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;
}