From 1a31376ddaedda8808e1f8fbde96ea5eea392132 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 19 Jun 2019 14:24:20 -0400 Subject: [PATCH] Allow configuration of exception handling through nested builder Issue: gh-5557 --- .../annotation/web/builders/HttpSecurity.java | 39 +++++++++++++ ...ingConfigurerAccessDeniedHandlerTests.java | 43 +++++++++++++- ...aceHttpServerAccessDeniedHandlerTests.java | 58 +++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 565acac30b..341515f5c3 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -681,6 +681,45 @@ public final class HttpSecurity extends return getOrApply(new ExceptionHandlingConfigurer<>()); } + /** + * Allows configuring exception handling. This is automatically applied when using + * {@link WebSecurityConfigurerAdapter}. + * + *

Example Custom Configuration

+ * + * The following customization will ensure that users who are denied access are forwarded + * to the page "/errors/access-denied". + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class ExceptionHandlingSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests()
+	 * 				.antMatchers("/**").hasRole("USER")
+	 * 				.and()
+	 * 			// sample exception handling customization
+	 * 			.exceptionHandling(exceptionHandling ->
+	 * 				exceptionHandling
+	 * 					.accessDeniedPage("/errors/access-denied")
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @param exceptionHandlingCustomizer the {@link Customizer} to provide more options for + * the {@link ExceptionHandlingConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity exceptionHandling(Customizer> exceptionHandlingCustomizer) throws Exception { + exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>())); + return HttpSecurity.this; + } + /** * Sets up management of the {@link SecurityContext} on the * {@link SecurityContextHolder} between {@link HttpServletRequest}'s. This is diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java index aad85a825b..b2f92d4247 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -86,6 +86,47 @@ public class ExceptionHandlingConfigurerAccessDeniedHandlerTests { } } + @Test + @WithMockUser(roles = "ANYTHING") + public void getWhenAccessDeniedOverriddenInLambdaThenCustomizesResponseByRequest() + throws Exception { + this.spring.register(RequestMatcherBasedAccessDeniedHandlerInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/hello")) + .andExpect(status().isIAmATeapot()); + + this.mvc.perform(get("/goodbye")) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class RequestMatcherBasedAccessDeniedHandlerInLambdaConfig extends WebSecurityConfigurerAdapter { + AccessDeniedHandler teapotDeniedHandler = + (request, response, exception) -> + response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().denyAll() + .and() + .exceptionHandling(exceptionHandling -> + exceptionHandling + .defaultAccessDeniedHandlerFor( + this.teapotDeniedHandler, + new AntPathRequestMatcher("/hello/**") + ) + .defaultAccessDeniedHandlerFor( + new AccessDeniedHandlerImpl(), + AnyRequestMatcher.INSTANCE + ) + ); + // @formatter:on + } + } + @Test @WithMockUser(roles = "ANYTHING") public void getWhenAccessDeniedOverriddenByOnlyOneHandlerThenAllRequestsUseThatHandler() diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java index eb4763bd55..b6ddacb237 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java @@ -83,6 +83,31 @@ public class NamespaceHttpServerAccessDeniedHandlerTests { return new UsernamePasswordAuthenticationToken("user", null, AuthorityUtils.NO_AUTHORITIES); } + @Test + public void requestWhenCustomAccessDeniedPageInLambdaThenForwardedToCustomPage() throws Exception { + this.spring.register(AccessDeniedPageInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .with(authentication(user()))) + .andExpect(status().isForbidden()) + .andExpect(forwardedUrl("/AccessDeniedPageConfig")); + } + + @EnableWebSecurity + static class AccessDeniedPageInLambdaConfig extends WebSecurityConfigurerAdapter { + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().denyAll() + .and() + .exceptionHandling(exceptionHandling -> + exceptionHandling.accessDeniedPage("/AccessDeniedPageConfig") + ); + // @formatter:on + } + } + @Test public void requestWhenCustomAccessDeniedHandlerThenBehaviorMatchesNamespace() throws Exception { this.spring.register(AccessDeniedHandlerRefConfig.class).autowire(); @@ -109,6 +134,39 @@ public class NamespaceHttpServerAccessDeniedHandlerTests { } } + @Test + public void requestWhenCustomAccessDeniedHandlerInLambdaThenBehaviorMatchesNamespace() throws Exception { + this.spring.register(AccessDeniedHandlerRefInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/") + .with(authentication(user()))); + + verify(AccessDeniedHandlerRefInLambdaConfig.accessDeniedHandler) + .handle(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AccessDeniedException.class)); + } + + @EnableWebSecurity + static class AccessDeniedHandlerRefInLambdaConfig extends WebSecurityConfigurerAdapter { + static AccessDeniedHandler accessDeniedHandler = mock(AccessDeniedHandler.class); + + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().denyAll() + .and() + .exceptionHandling(exceptionHandling -> + exceptionHandling.accessDeniedHandler(accessDeniedHandler()) + ); + // @formatter:on + } + + @Bean + AccessDeniedHandler accessDeniedHandler() { + return accessDeniedHandler; + } + } + private T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); }