From fed15f2b01b763158f6650afa130592033669740 Mon Sep 17 00:00:00 2001 From: Denys Ivano Date: Mon, 7 May 2018 19:09:01 +0300 Subject: [PATCH] Add accessDeniedHandler method to ExceptionHandlingSpec This allows to configure accessDeniedHandler in ExceptionTranslationWebFilter through ServerHttpSecurity. Issue: gh-5257 --- .../config/web/server/ServerHttpSecurity.java | 17 +++ .../server/ExceptionHandlingSpecTests.java | 143 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 6c55d97484..de8bd44274 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -171,6 +171,8 @@ public class ServerHttpSecurity { private List defaultEntryPoints = new ArrayList<>(); + private ServerAccessDeniedHandler accessDeniedHandler; + private List webFilters = new ArrayList<>(); private Throwable built; @@ -526,6 +528,9 @@ public class ServerHttpSecurity { exceptionTranslationWebFilter.setAuthenticationEntryPoint( authenticationEntryPoint); } + if(accessDeniedHandler != null) { + exceptionTranslationWebFilter.setAccessDeniedHandler(accessDeniedHandler); + } this.addFilterAt(exceptionTranslationWebFilter, SecurityWebFiltersOrder.EXCEPTION_TRANSLATION); this.authorizeExchange.configure(this); } @@ -793,6 +798,18 @@ public class ServerHttpSecurity { return this; } + /** + * Configures what to do when an authenticated user does not hold a required authority + * @param accessDeniedHandler the access denied handler to use + * @return the {@link ExceptionHandlingSpec} to configure + * + * @since 5.0.5 + */ + public ExceptionHandlingSpec accessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) { + ServerHttpSecurity.this.accessDeniedHandler = accessDeniedHandler; + return this; + } + /** * Allows method chaining to continue configuring the {@link ServerHttpSecurity} * @return the {@link ServerHttpSecurity} to continue configuring diff --git a/config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java new file mode 100644 index 0000000000..e88ab4848a --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.config.web.server; + +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; +import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.ServerAuthenticationEntryPoint; +import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint; +import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler; +import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; +import org.springframework.test.web.reactive.server.WebTestClient; +import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials; +import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; + +/** + * @author Denys Ivano + * @since 5.0.5 + */ +public class ExceptionHandlingSpecTests { + private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); + + @Test + public void defaultAuthenticationEntryPoint() { + SecurityWebFilterChain securityWebFilter = this.http + .csrf().disable() + .authorizeExchange() + .anyExchange().authenticated() + .and() + .exceptionHandling() + .and() + .build(); + + WebTestClient client = WebTestClientBuilder + .bindToWebFilters(securityWebFilter) + .build(); + + client + .get() + .uri("/test") + .exchange() + .expectStatus().isUnauthorized() + .expectHeader().valueMatches("WWW-Authenticate", "Basic.*"); + } + + @Test + public void customAuthenticationEntryPoint() { + SecurityWebFilterChain securityWebFilter = this.http + .csrf().disable() + .authorizeExchange() + .anyExchange().authenticated() + .and() + .exceptionHandling() + .authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth")) + .and() + .build(); + + WebTestClient client = WebTestClientBuilder + .bindToWebFilters(securityWebFilter) + .build(); + + client + .get() + .uri("/test") + .exchange() + .expectStatus().isFound() + .expectHeader().valueMatches("Location", ".*"); + } + + @Test + public void defaultAccessDeniedHandler() { + SecurityWebFilterChain securityWebFilter = this.http + .csrf().disable() + .httpBasic().and() + .authorizeExchange() + .anyExchange().hasRole("ADMIN") + .and() + .exceptionHandling() + .and() + .build(); + + WebTestClient client = WebTestClientBuilder + .bindToWebFilters(securityWebFilter) + .filter(basicAuthentication()) + .build(); + + client + .get() + .uri("/admin") + .attributes(basicAuthenticationCredentials("user", "password")) + .exchange() + .expectStatus().isForbidden(); + } + + @Test + public void customAccessDeniedHandler() { + SecurityWebFilterChain securityWebFilter = this.http + .csrf().disable() + .httpBasic().and() + .authorizeExchange() + .anyExchange().hasRole("ADMIN") + .and() + .exceptionHandling() + .accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST)) + .and() + .build(); + + WebTestClient client = WebTestClientBuilder + .bindToWebFilters(securityWebFilter) + .filter(basicAuthentication()) + .build(); + + client + .get() + .uri("/admin") + .attributes(basicAuthenticationCredentials("user", "password")) + .exchange() + .expectStatus().isBadRequest(); + } + + private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) { + return new RedirectServerAuthenticationEntryPoint(location); + } + + private ServerAccessDeniedHandler httpStatusServerAccessDeniedHandler(HttpStatus httpStatus) { + return new HttpStatusServerAccessDeniedHandler(httpStatus); + } +}