From cee8b874a347c737e16fae822cc52291c598032d Mon Sep 17 00:00:00 2001 From: DingHao Date: Thu, 4 Jul 2024 11:07:39 +0800 Subject: [PATCH] Method Security Exclude ExceptionHandler annotation method Closes gh-15352 --- .../method/AuthorizationMethodPointcuts.java | 28 ++++- ...tyMockMvcRequestExceptionHandlerTests.java | 103 ++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestExceptionHandlerTests.java diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java index cbdba35f3a6..6745b02781e 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -17,27 +17,44 @@ package org.springframework.security.authorization.method; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import org.springframework.aop.Pointcut; import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.Pointcuts; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.aop.support.annotation.AnnotationMethodMatcher; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; +import org.springframework.util.ClassUtils; /** * @author Josh Cummings * @author Evgeniy Cheban + * @author DingHao */ final class AuthorizationMethodPointcuts { + private static Class exceptionHandlerClass; + + static { + try { + exceptionHandlerClass = ClassUtils + .resolveClassName("org.springframework.web.bind.annotation.ExceptionHandler", null); + } + catch (Exception ex) { + exceptionHandlerClass = null; + } + } + static Pointcut forAllAnnotations() { return forAnnotations(PreFilter.class, PreAuthorize.class, PostFilter.class, PostAuthorize.class); } @SafeVarargs + @SuppressWarnings("unchecked") static Pointcut forAnnotations(Class... annotations) { ComposablePointcut pointcut = null; for (Class annotation : annotations) { @@ -48,6 +65,15 @@ static Pointcut forAnnotations(Class... annotations) { pointcut.union(classOrMethod(annotation)); } } + if (exceptionHandlerClass != null && pointcut != null) { + pointcut + .intersection(new AnnotationMethodMatcher((Class) exceptionHandlerClass, true) { + @Override + public boolean matches(Method method, Class targetClass) { + return !super.matches(method, targetClass); + } + }); + } return pointcut; } diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestExceptionHandlerTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestExceptionHandlerTests.java new file mode 100644 index 00000000000..4d8e8fbda02 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestExceptionHandlerTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2002-2024 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.security.test.web.servlet.request; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * SecurityMockMvcRequestExceptionHandlerTests + * + * @author DingHao + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = SecurityMockMvcRequestExceptionHandlerTests.Config.class) +@WebAppConfiguration +public class SecurityMockMvcRequestExceptionHandlerTests { + + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @BeforeEach + public void setup() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + @WithMockUser + public void testMethodSecurityWithExceptionHandler() throws Exception { + this.mvc.perform(get("/")).andExpect(status().is2xxSuccessful()); + } + + @Configuration + @EnableWebSecurity + @EnableMethodSecurity + @EnableWebMvc + static class Config { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests((c) -> c.anyRequest().authenticated()).build(); + } + + @RestController + @PreAuthorize("hasRole('ADMIN')") + static class TestRestApi { + + @RequestMapping("/") + String get() { + return "Hello"; + } + + @ExceptionHandler + private ResponseEntity handleAccessDeniedException(AccessDeniedException e) { + return ResponseEntity.ok(e.getMessage()); + } + + } + + } + +}