Skip to content

Commit 2b2c0da

Browse files
committed
Method Security Exclude ExceptionHandler annotation method
Closes gh-15352
1 parent a309552 commit 2b2c0da

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

core/src/main/java/org/springframework/security/authorization/method/AuthorizationMethodPointcuts.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,24 +20,41 @@
2020

2121
import org.springframework.aop.Pointcut;
2222
import org.springframework.aop.support.ComposablePointcut;
23+
import org.springframework.aop.support.MethodMatchers;
2324
import org.springframework.aop.support.Pointcuts;
2425
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
26+
import org.springframework.aop.support.annotation.AnnotationMethodMatcher;
2527
import org.springframework.security.access.prepost.PostAuthorize;
2628
import org.springframework.security.access.prepost.PostFilter;
2729
import org.springframework.security.access.prepost.PreAuthorize;
2830
import org.springframework.security.access.prepost.PreFilter;
31+
import org.springframework.util.ClassUtils;
2932

3033
/**
3134
* @author Josh Cummings
3235
* @author Evgeniy Cheban
36+
* @author DingHao
3337
*/
3438
final class AuthorizationMethodPointcuts {
3539

40+
private static Class<?> exceptionHandlerClass;
41+
42+
static {
43+
try {
44+
exceptionHandlerClass = ClassUtils
45+
.resolveClassName("org.springframework.web.bind.annotation.ExceptionHandler", null);
46+
}
47+
catch (Exception ex) {
48+
exceptionHandlerClass = null;
49+
}
50+
}
51+
3652
static Pointcut forAllAnnotations() {
3753
return forAnnotations(PreFilter.class, PreAuthorize.class, PostFilter.class, PostAuthorize.class);
3854
}
3955

4056
@SafeVarargs
57+
@SuppressWarnings("unchecked")
4158
static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
4259
ComposablePointcut pointcut = null;
4360
for (Class<? extends Annotation> annotation : annotations) {
@@ -48,6 +65,10 @@ static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
4865
pointcut.union(classOrMethod(annotation));
4966
}
5067
}
68+
if (exceptionHandlerClass != null && pointcut != null) {
69+
pointcut.intersection(MethodMatchers
70+
.negate(new AnnotationMethodMatcher((Class<? extends Annotation>) exceptionHandlerClass, true)));
71+
}
5172
return pointcut;
5273
}
5374

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.test.web.servlet.request;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.extension.ExtendWith;
22+
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.http.ResponseEntity;
27+
import org.springframework.security.access.AccessDeniedException;
28+
import org.springframework.security.access.prepost.PreAuthorize;
29+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
30+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
31+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
32+
import org.springframework.security.test.context.support.WithMockUser;
33+
import org.springframework.security.web.SecurityFilterChain;
34+
import org.springframework.test.context.ContextConfiguration;
35+
import org.springframework.test.context.junit.jupiter.SpringExtension;
36+
import org.springframework.test.context.web.WebAppConfiguration;
37+
import org.springframework.test.web.servlet.MockMvc;
38+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
39+
import org.springframework.web.bind.annotation.ExceptionHandler;
40+
import org.springframework.web.bind.annotation.RequestMapping;
41+
import org.springframework.web.bind.annotation.RestController;
42+
import org.springframework.web.context.WebApplicationContext;
43+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
44+
45+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
46+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
47+
48+
/**
49+
* SecurityMockMvcRequestExceptionHandlerTests
50+
*
51+
* @author DingHao
52+
*/
53+
@ExtendWith(SpringExtension.class)
54+
@ContextConfiguration(classes = SecurityMockMvcRequestExceptionHandlerTests.Config.class)
55+
@WebAppConfiguration
56+
public class SecurityMockMvcRequestExceptionHandlerTests {
57+
58+
@Autowired
59+
private WebApplicationContext context;
60+
61+
private MockMvc mvc;
62+
63+
@BeforeEach
64+
public void setup() {
65+
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
66+
}
67+
68+
@Test
69+
@WithMockUser
70+
public void testMethodSecurityWithExceptionHandler() throws Exception {
71+
this.mvc.perform(get("/")).andExpect(status().is2xxSuccessful());
72+
}
73+
74+
@Configuration
75+
@EnableWebSecurity
76+
@EnableMethodSecurity
77+
@EnableWebMvc
78+
static class Config {
79+
80+
@Bean
81+
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
82+
return http.authorizeHttpRequests((c) -> c.anyRequest().authenticated()).build();
83+
}
84+
85+
@RestController
86+
@PreAuthorize("hasRole('ADMIN')")
87+
static class TestRestApi {
88+
89+
@RequestMapping("/")
90+
String get() {
91+
return "Hello";
92+
}
93+
94+
@ExceptionHandler
95+
private ResponseEntity<String> handleAccessDeniedException(AccessDeniedException e) {
96+
return ResponseEntity.ok(e.getMessage());
97+
}
98+
99+
}
100+
101+
}
102+
103+
}

0 commit comments

Comments
 (0)