accessDeniedHandler,
ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
ResourceServerHttpSecurityPostProcessor httpPostProcessor) {
@@ -36,7 +39,10 @@ public static ServerHttpSecurity configureResourceServer(
ReactiveConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(), addonsResourceServerProperties.getCsrf());
ReactiveConfigurationSupport.configureAccess(http, addonsResourceServerProperties.getPermitAll());
- http.exceptionHandling(handling -> handling.accessDeniedHandler(accessDeniedHandler));
+ http.exceptionHandling(handling -> {
+ handling.authenticationEntryPoint(authenticationEntryPoint);
+ accessDeniedHandler.ifPresent(handling::accessDeniedHandler);
+ });
if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
http.redirectToHttps(withDefaults());
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
index fb49a08e5..1183df565 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
@@ -19,15 +19,15 @@
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -37,7 +37,12 @@
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerReactiveAuthenticationManagerResolver;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
+import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
+import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.csrf.CsrfToken;
import org.springframework.util.StringUtils;
@@ -60,22 +65,22 @@
/**
*
* Usage
- * If not using spring-boot, @Import or @ComponentScan this class. All beans defined here are @ConditionalOnMissingBean => just define your own
- * @Beans to override.
+ * If not using spring-boot, @Import or @ComponentScan this class. All beans defined here are @ConditionalOnMissingBean =>
+ * just define your own @Beans to override.
*
*
* Provided @Beans
*
*
- * - SecurityWebFilterChain: applies CORS, CSRF, anonymous, sessionCreationPolicy, SSL redirect and 401 instead of redirect to login properties as
- * defined in {@link SpringAddonsOidcProperties}
- * - AuthorizeExchangeSpecPostProcessor. Override if you need fined grained HTTP security (more than authenticated() to all routes but the ones defined
- * as permitAll() in {@link SpringAddonsOidcProperties}
+ * - SecurityWebFilterChain: applies CORS, CSRF, anonymous, sessionCreationPolicy, SSL redirect and 401 instead of redirect to
+ * login properties as defined in {@link SpringAddonsOidcProperties}
+ * - AuthorizeExchangeSpecPostProcessor. Override if you need fined grained HTTP security (more than authenticated() to all routes
+ * but the ones defined as permitAll() in {@link SpringAddonsOidcProperties}
* - Jwt2AuthoritiesConverter: responsible for converting the JWT into Collection<? extends GrantedAuthority>
- * - ReactiveJwt2OpenidClaimSetConverter<T extends Map<String, Object> & Serializable>: responsible for converting the JWT into a
- * claim-set of your choice (OpenID or not)
- * - ReactiveJwt2AuthenticationConverter<OAuthentication<T extends OpenidClaimSet>>: responsible for converting the JWT into an
- * Authentication (uses both beans above)
+ * - ReactiveJwt2OpenidClaimSetConverter<T extends Map<String, Object> & Serializable>: responsible for converting
+ * the JWT into a claim-set of your choice (OpenID or not)
+ * - ReactiveJwt2AuthenticationConverter<OAuthentication<T extends OpenidClaimSet>>: responsible for converting the JWT
+ * into an Authentication (uses both beans above)
* - ReactiveAuthenticationManagerResolver: required to be able to define more than one token issuer until
* https://github.com/spring-projects/spring-boot/issues/30108 is solved
*
@@ -91,8 +96,8 @@ public class ReactiveSpringAddonsOidcResourceServerBeans {
/**
*
- * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no security matcher and
- * an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers.
+ * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no
+ * security matcher and an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers.
*
*
* You should consider to set security matcher to all other {@link SecurityWebFilterChain} beans and provide a
@@ -106,9 +111,10 @@ public class ReactiveSpringAddonsOidcResourceServerBeans {
* @param authorizePostProcessor Hook to override access-control rules for all path that are not listed in "permit-all"
* @param httpPostProcessor Hook to override all or part of HttpSecurity auto-configuration
* @param authenticationManagerResolver Converts successful JWT decoding result into an {@link Authentication}
- * @param accessDeniedHandler handler for unauthorized requests (missing or invalid access-token)
- * @return A default {@link SecurityWebFilterChain} for reactive resource-servers with JWT decoder(matches all unmatched
- * routes with lowest precedence)
+ * @param authenticationEntryPoint The {@link AuthenticationEntryPoint} to use (defaults returns 401)
+ * @param accessDeniedHandler An optional {@link AccessDeniedHandler} to use instead of Boot default one
+ * @return A default {@link SecurityWebFilterChain} for reactive resource-servers with JWT decoder(matches all
+ * unmatched routes with lowest precedence)
*/
@Conditional(IsJwtDecoderResourceServerCondition.class)
@Order(Ordered.LOWEST_PRECEDENCE)
@@ -120,24 +126,27 @@ SecurityWebFilterChain springAddonsJwtResourceServerSecurityFilterChain(
ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
ResourceServerHttpSecurityPostProcessor httpPostProcessor,
ReactiveAuthenticationManagerResolver authenticationManagerResolver,
- ServerAccessDeniedHandler accessDeniedHandler) {
+ ServerAuthenticationEntryPoint authenticationEntryPoint,
+ Optional accessDeniedHandler) {
http.oauth2ResourceServer(server -> server.authenticationManagerResolver(authenticationManagerResolver));
- ReactiveConfigurationSupport.configureResourceServer(
- http,
- serverProperties,
- addonsProperties.getResourceserver(),
- accessDeniedHandler,
- authorizePostProcessor,
- httpPostProcessor);
+ ReactiveConfigurationSupport
+ .configureResourceServer(
+ http,
+ serverProperties,
+ addonsProperties.getResourceserver(),
+ authenticationEntryPoint,
+ accessDeniedHandler,
+ authorizePostProcessor,
+ httpPostProcessor);
return http.build();
}
/**
*
- * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no security matcher and
- * an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers.
+ * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no
+ * security matcher and an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers.
*
*
* You should consider to set security matcher to all other {@link SecurityWebFilterChain} beans and provide a
@@ -151,9 +160,10 @@ SecurityWebFilterChain springAddonsJwtResourceServerSecurityFilterChain(
* @param authorizePostProcessor Hook to override access-control rules for all path that are not listed in "permit-all"
* @param httpPostProcessor Hook to override all or part of HttpSecurity auto-configuration
* @param introspectionAuthenticationConverter Converts successful introspection result into an {@link Authentication}
- * @param accessDeniedHandler handler for unauthorized requests (missing or invalid access-token)
- * @return A default {@link SecurityWebFilterChain} for reactive resource-servers with access-token introspection
- * (matches all unmatched routes with lowest precedence)
+ * @param authenticationEntryPoint The {@link AuthenticationEntryPoint} to use (defaults returns 401)
+ * @param accessDeniedHandler An optional {@link AccessDeniedHandler} to use instead of Boot default one
+ * @return A default {@link SecurityWebFilterChain} for reactive resource-servers with access-token
+ * introspection (matches all unmatched routes with lowest precedence)
*/
@Conditional(IsIntrospectingResourceServerCondition.class)
@Order(Ordered.LOWEST_PRECEDENCE)
@@ -164,21 +174,24 @@ SecurityWebFilterChain springAddonsIntrospectingResourceServerSecurityFilterChai
SpringAddonsOidcProperties addonsProperties,
ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
ResourceServerHttpSecurityPostProcessor httpPostProcessor,
- org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter introspectionAuthenticationConverter,
- org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector,
- ServerAccessDeniedHandler accessDeniedHandler) {
+ ReactiveOpaqueTokenAuthenticationConverter introspectionAuthenticationConverter,
+ ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector,
+ ServerAuthenticationEntryPoint authenticationEntryPoint,
+ Optional accessDeniedHandler) {
http.oauth2ResourceServer(server -> server.opaqueToken(ot -> {
ot.introspector(opaqueTokenIntrospector);
ot.authenticationConverter(introspectionAuthenticationConverter);
}));
- ReactiveConfigurationSupport.configureResourceServer(
- http,
- serverProperties,
- addonsProperties.getResourceserver(),
- accessDeniedHandler,
- authorizePostProcessor,
- httpPostProcessor);
+ ReactiveConfigurationSupport
+ .configureResourceServer(
+ http,
+ serverProperties,
+ addonsProperties.getResourceserver(),
+ authenticationEntryPoint,
+ accessDeniedHandler,
+ authorizePostProcessor,
+ httpPostProcessor);
return http.build();
}
@@ -195,10 +208,11 @@ ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor() {
}
/**
- * Hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify anything
+ * Hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can
+ * modify anything
*
- * @return a hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify
- * anything
+ * @return a hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that
+ * you can modify anything
*/
@ConditionalOnMissingBean
@Bean
@@ -231,12 +245,17 @@ ReactiveAuthenticationManagerResolver authenticationManagerRe
final Map> jwtManagers =
Stream.of(addonsProperties.getOps()).collect(Collectors.toMap(issuer -> issuer.getIss().toString(), issuer -> {
- final var decoder = issuer.getJwkSetUri() != null && StringUtils.hasLength(issuer.getJwkSetUri().toString())
- ? NimbusReactiveJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build()
- : NimbusReactiveJwtDecoder.withIssuerLocation(issuer.getIss().toString()).build();
+ final var decoder =
+ issuer.getJwkSetUri() != null && StringUtils.hasLength(issuer.getJwkSetUri().toString())
+ ? NimbusReactiveJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build()
+ : NimbusReactiveJwtDecoder.withIssuerLocation(issuer.getIss().toString()).build();
- final OAuth2TokenValidator defaultValidator = Optional.ofNullable(issuer.getIss()).map(URI::toString)
- .map(JwtValidators::createDefaultWithIssuer).orElse(JwtValidators.createDefault());
+ final OAuth2TokenValidator defaultValidator =
+ Optional
+ .ofNullable(issuer.getIss())
+ .map(URI::toString)
+ .map(JwtValidators::createDefaultWithIssuer)
+ .orElse(JwtValidators.createDefault());
// If the spring-addons conf for resource server contains a non empty audience, add an audience validator
// @formatter:off
@@ -255,10 +274,11 @@ ReactiveAuthenticationManagerResolver authenticationManagerRe
return Mono.just(provider);
}));
- log.debug(
- "Building default JwtIssuerReactiveAuthenticationManagerResolver with: {} {}",
- auth2ResourceServerProperties.getJwt(),
- Stream.of(addonsProperties.getOps()).toList());
+ log
+ .debug(
+ "Building default JwtIssuerReactiveAuthenticationManagerResolver with: {} {}",
+ auth2ResourceServerProperties.getJwt(),
+ Stream.of(addonsProperties.getOps()).toList());
return new JwtIssuerReactiveAuthenticationManagerResolver(issuerLocation -> jwtManagers.getOrDefault(issuerLocation, Mono.empty()));
}
@@ -269,12 +289,11 @@ ReactiveAuthenticationManagerResolver authenticationManagerRe
*/
@ConditionalOnMissingBean
@Bean
- ServerAccessDeniedHandler serverAccessDeniedHandler() {
- log.debug("Building default ServerAccessDeniedHandler");
- return (var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> {
+ ServerAuthenticationEntryPoint authenticationEntryPoint() {
+ return (ServerWebExchange exchange, AuthenticationException ex) -> exchange.getPrincipal().flatMap(principal -> {
var response = exchange.getResponse();
- response.setStatusCode(principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN);
- response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
+ response.setStatusCode(HttpStatus.UNAUTHORIZED);
+ response.getHeaders().set(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\"");
var dataBufferFactory = response.bufferFactory();
var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset()));
return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer));