-
Notifications
You must be signed in to change notification settings - Fork 40.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Deny unauthorized access to the error page
Fixes gh-26356 Co-authored-by Andy Wilkinson <wilkinsona@vmware.com>
- Loading branch information
Showing
13 changed files
with
377 additions
and
11 deletions.
There are no files selected for viewing
50 changes: 50 additions & 0 deletions
50
...ngframework/boot/autoconfigure/security/servlet/ErrorPageSecurityFilterConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Copyright 2012-2021 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 | ||
* | ||
* https://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.boot.autoconfigure.security.servlet; | ||
|
||
import java.util.EnumSet; | ||
|
||
import javax.servlet.DispatcherType; | ||
|
||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; | ||
import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||
import org.springframework.boot.web.servlet.filter.ErrorPageSecurityFilter; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; | ||
|
||
/** | ||
* Configures the {@link ErrorPageSecurityFilter}. | ||
* | ||
* @author Madhura Bhave | ||
*/ | ||
@Configuration(proxyBeanMethods = false) | ||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) | ||
class ErrorPageSecurityFilterConfiguration { | ||
|
||
@Bean | ||
@ConditionalOnBean(WebInvocationPrivilegeEvaluator.class) | ||
FilterRegistrationBean<ErrorPageSecurityFilter> errorPageSecurityInterceptor(ApplicationContext context) { | ||
FilterRegistrationBean<ErrorPageSecurityFilter> registration = new FilterRegistrationBean<>( | ||
new ErrorPageSecurityFilter(context)); | ||
registration.setDispatcherTypes(EnumSet.of(DispatcherType.ERROR)); | ||
return registration; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
...ot/src/main/java/org/springframework/boot/web/servlet/filter/ErrorPageSecurityFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* Copyright 2012-2021 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 | ||
* | ||
* https://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.boot.web.servlet.filter; | ||
|
||
import java.io.IOException; | ||
|
||
import javax.servlet.FilterChain; | ||
import javax.servlet.RequestDispatcher; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.http.HttpFilter; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
|
||
import org.springframework.beans.factory.NoSuchBeanDefinitionException; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; | ||
|
||
/** | ||
* {@link HttpFilter} that intercepts error dispatches to ensure authorized access to the | ||
* error page. | ||
* | ||
* @author Madhura Bhave | ||
* @author Andy Wilkinson | ||
* @since 2.6.0 | ||
*/ | ||
public class ErrorPageSecurityFilter extends HttpFilter { | ||
|
||
private static final WebInvocationPrivilegeEvaluator ALWAYS = new AlwaysAllowWebInvocationPrivilegeEvaluator(); | ||
|
||
private final ApplicationContext context; | ||
|
||
private volatile WebInvocationPrivilegeEvaluator privilegeEvaluator; | ||
|
||
public ErrorPageSecurityFilter(ApplicationContext context) { | ||
this.context = context; | ||
} | ||
|
||
@Override | ||
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||
throws IOException, ServletException { | ||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||
if (!getPrivilegeEvaluator().isAllowed(request.getRequestURI(), authentication)) { | ||
sendError(request, response); | ||
return; | ||
} | ||
chain.doFilter(request, response); | ||
} | ||
|
||
private WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() { | ||
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.privilegeEvaluator; | ||
if (privilegeEvaluator == null) { | ||
privilegeEvaluator = getPrivilegeEvaluatorBean(); | ||
this.privilegeEvaluator = privilegeEvaluator; | ||
} | ||
return privilegeEvaluator; | ||
} | ||
|
||
private WebInvocationPrivilegeEvaluator getPrivilegeEvaluatorBean() { | ||
try { | ||
return this.context.getBean(WebInvocationPrivilegeEvaluator.class); | ||
} | ||
catch (NoSuchBeanDefinitionException ex) { | ||
return ALWAYS; | ||
} | ||
} | ||
|
||
private void sendError(HttpServletRequest request, HttpServletResponse response) throws IOException { | ||
Integer errorCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); | ||
response.sendError((errorCode != null) ? errorCode : 401); | ||
} | ||
|
||
/** | ||
* {@link WebInvocationPrivilegeEvaluator} that always allows access. | ||
*/ | ||
private static class AlwaysAllowWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator { | ||
|
||
@Override | ||
public boolean isAllowed(String uri, Authentication authentication) { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) { | ||
return true; | ||
} | ||
|
||
} | ||
|
||
} |
98 changes: 98 additions & 0 deletions
98
...c/test/java/org/springframework/boot/web/servlet/filter/ErrorPageSecurityFilterTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright 2012-2021 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 | ||
* | ||
* https://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.boot.web.servlet.filter; | ||
|
||
import javax.servlet.FilterChain; | ||
import javax.servlet.RequestDispatcher; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import org.springframework.beans.factory.NoSuchBeanDefinitionException; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.mock.web.MockHttpServletRequest; | ||
import org.springframework.mock.web.MockHttpServletResponse; | ||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.ArgumentMatchers.anyString; | ||
import static org.mockito.BDDMockito.given; | ||
import static org.mockito.BDDMockito.willThrow; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.verifyNoInteractions; | ||
|
||
/** | ||
* Tests for {@link ErrorPageSecurityFilter}. | ||
* | ||
* @author Madhura Bhave | ||
*/ | ||
class ErrorPageSecurityFilterTests { | ||
|
||
private final WebInvocationPrivilegeEvaluator privilegeEvaluator = mock(WebInvocationPrivilegeEvaluator.class); | ||
|
||
private final ApplicationContext context = mock(ApplicationContext.class); | ||
|
||
private final MockHttpServletRequest request = new MockHttpServletRequest(); | ||
|
||
private final MockHttpServletResponse response = new MockHttpServletResponse(); | ||
|
||
private final FilterChain filterChain = mock(FilterChain.class); | ||
|
||
private ErrorPageSecurityFilter securityFilter; | ||
|
||
@BeforeEach | ||
void setup() { | ||
given(this.context.getBean(WebInvocationPrivilegeEvaluator.class)).willReturn(this.privilegeEvaluator); | ||
this.securityFilter = new ErrorPageSecurityFilter(this.context); | ||
} | ||
|
||
@Test | ||
void whenAccessIsAllowedShouldContinueDownFilterChain() throws Exception { | ||
given(this.privilegeEvaluator.isAllowed(anyString(), any())).willReturn(true); | ||
this.securityFilter.doFilter(this.request, this.response, this.filterChain); | ||
verify(this.filterChain).doFilter(this.request, this.response); | ||
} | ||
|
||
@Test | ||
void whenAccessIsDeniedShouldCallSendError() throws Exception { | ||
given(this.privilegeEvaluator.isAllowed(anyString(), any())).willReturn(false); | ||
this.request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 403); | ||
this.securityFilter.doFilter(this.request, this.response, this.filterChain); | ||
verifyNoInteractions(this.filterChain); | ||
assertThat(this.response.getStatus()).isEqualTo(403); | ||
} | ||
|
||
@Test | ||
void whenAccessIsDeniedAndNoErrorCodeAttributeOnRequest() throws Exception { | ||
given(this.privilegeEvaluator.isAllowed(anyString(), any())).willReturn(false); | ||
this.securityFilter.doFilter(this.request, this.response, this.filterChain); | ||
verifyNoInteractions(this.filterChain); | ||
assertThat(this.response.getStatus()).isEqualTo(401); | ||
} | ||
|
||
@Test | ||
void whenPrivilegeEvaluatorIsNotPresentAccessIsAllowed() throws Exception { | ||
ApplicationContext context = mock(ApplicationContext.class); | ||
willThrow(NoSuchBeanDefinitionException.class).given(context).getBean(WebInvocationPrivilegeEvaluator.class); | ||
ErrorPageSecurityFilter securityFilter = new ErrorPageSecurityFilter(context); | ||
securityFilter.doFilter(this.request, this.response, this.filterChain); | ||
verify(this.filterChain).doFilter(this.request, this.response); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.