33
33
import io .micrometer .observation .ObservationRegistry ;
34
34
import io .micrometer .observation .ObservationTextPublisher ;
35
35
import jakarta .annotation .security .DenyAll ;
36
+ import org .aopalliance .aop .Advice ;
36
37
import org .aopalliance .intercept .MethodInterceptor ;
37
38
import org .aopalliance .intercept .MethodInvocation ;
38
39
import org .junit .jupiter .api .Test ;
42
43
import org .mockito .Mockito ;
43
44
44
45
import org .springframework .aop .Advisor ;
46
+ import org .springframework .aop .Pointcut ;
45
47
import org .springframework .aop .config .AopConfigUtils ;
46
48
import org .springframework .aop .support .DefaultPointcutAdvisor ;
47
49
import org .springframework .aop .support .JdkRegexpMethodPointcut ;
62
64
import org .springframework .core .annotation .AnnotationAwareOrderComparator ;
63
65
import org .springframework .core .annotation .AnnotationConfigurationException ;
64
66
import org .springframework .core .annotation .Order ;
67
+ import org .springframework .http .HttpStatus ;
65
68
import org .springframework .http .HttpStatusCode ;
69
+ import org .springframework .http .MediaType ;
66
70
import org .springframework .http .ResponseEntity ;
67
71
import org .springframework .security .access .AccessDeniedException ;
68
72
import org .springframework .security .access .PermissionEvaluator ;
95
99
import org .springframework .security .authorization .method .MethodInvocationResult ;
96
100
import org .springframework .security .authorization .method .PrePostTemplateDefaults ;
97
101
import org .springframework .security .config .annotation .SecurityContextChangedListenerConfig ;
102
+ import org .springframework .security .config .annotation .web .configuration .EnableWebSecurity ;
98
103
import org .springframework .security .config .core .GrantedAuthorityDefaults ;
99
104
import org .springframework .security .config .observation .SecurityObservationSettings ;
100
105
import org .springframework .security .config .test .SpringTestContext ;
107
112
import org .springframework .security .test .context .support .WithMockUser ;
108
113
import org .springframework .security .test .context .support .WithSecurityContextTestExecutionListener ;
109
114
import org .springframework .stereotype .Component ;
115
+ import org .springframework .stereotype .Service ;
110
116
import org .springframework .test .context .ContextConfiguration ;
111
117
import org .springframework .test .context .TestExecutionListeners ;
112
118
import org .springframework .test .context .junit .jupiter .SpringExtension ;
119
+ import org .springframework .test .web .servlet .MockMvc ;
120
+ import org .springframework .test .web .servlet .MvcResult ;
121
+ import org .springframework .test .web .servlet .request .MockHttpServletRequestBuilder ;
122
+ import org .springframework .web .bind .annotation .ControllerAdvice ;
123
+ import org .springframework .web .bind .annotation .ExceptionHandler ;
124
+ import org .springframework .web .bind .annotation .GetMapping ;
125
+ import org .springframework .web .bind .annotation .RequestParam ;
126
+ import org .springframework .web .bind .annotation .RestController ;
113
127
import org .springframework .web .context .ConfigurableWebApplicationContext ;
114
128
import org .springframework .web .context .support .AnnotationConfigWebApplicationContext ;
115
129
import org .springframework .web .servlet .ModelAndView ;
130
+ import org .springframework .web .servlet .config .annotation .EnableWebMvc ;
116
131
117
132
import static org .assertj .core .api .Assertions .assertThat ;
118
133
import static org .assertj .core .api .Assertions .assertThatExceptionOfType ;
127
142
import static org .mockito .Mockito .times ;
128
143
import static org .mockito .Mockito .verify ;
129
144
import static org .mockito .Mockito .verifyNoInteractions ;
145
+ import static org .springframework .security .test .web .servlet .request .SecurityMockMvcRequestPostProcessors .user ;
146
+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .get ;
147
+ import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .status ;
130
148
131
149
/**
132
150
* Tests for {@link PrePostMethodSecurityConfiguration}.
@@ -148,6 +166,9 @@ public class PrePostMethodSecurityConfigurationTests {
148
166
@ Autowired (required = false )
149
167
BusinessService businessService ;
150
168
169
+ @ Autowired (required = false )
170
+ MockMvc mvc ;
171
+
151
172
@ WithMockUser
152
173
@ Test
153
174
public void customMethodSecurityPreAuthorizeAdminWhenRoleUserThenAccessDeniedException () {
@@ -1181,6 +1202,80 @@ void autowireWhenDefaultsThenAdvisorAnnotationsAreSorted() {
1181
1202
}
1182
1203
}
1183
1204
1205
+ @ Test
1206
+ void getWhenPostAuthorizeAuthenticationNameMatchesThenRespondsWithOk () throws Exception {
1207
+ this .spring .register (WebMvcMethodSecurityConfig .class , BasicController .class ).autowire ();
1208
+ // @formatter:off
1209
+ MockHttpServletRequestBuilder requestWithUser = get ("/authorized-person" )
1210
+ .param ("name" , "rob" )
1211
+ .with (user ("rob" ));
1212
+ // @formatter:on
1213
+ this .mvc .perform (requestWithUser ).andExpect (status ().isOk ());
1214
+ }
1215
+
1216
+ @ Test
1217
+ void getWhenPostAuthorizeAuthenticationNameNotMatchThenRespondsWithForbidden () throws Exception {
1218
+ this .spring .register (WebMvcMethodSecurityConfig .class , BasicController .class ).autowire ();
1219
+ // @formatter:off
1220
+ MockHttpServletRequestBuilder requestWithUser = get ("/authorized-person" )
1221
+ .param ("name" , "john" )
1222
+ .with (user ("rob" ));
1223
+ // @formatter:on
1224
+ this .mvc .perform (requestWithUser ).andExpect (status ().isForbidden ());
1225
+ }
1226
+
1227
+ @ Test
1228
+ void getWhenPostAuthorizeWithinServiceAuthenticationNameMatchesThenRespondsWithOk () throws Exception {
1229
+ this .spring .register (WebMvcMethodSecurityConfig .class , BasicController .class , BasicService .class ).autowire ();
1230
+ // @formatter:off
1231
+ MockHttpServletRequestBuilder requestWithUser = get ("/greetings/authorized-person" )
1232
+ .param ("name" , "rob" )
1233
+ .with (user ("rob" ));
1234
+ // @formatter:on
1235
+ MvcResult mvcResult = this .mvc .perform (requestWithUser ).andExpect (status ().isOk ()).andReturn ();
1236
+ assertThat (mvcResult .getResponse ().getContentAsString ()).isEqualTo ("Hello: rob" );
1237
+ }
1238
+
1239
+ @ Test
1240
+ void getWhenPostAuthorizeWithinServiceAuthenticationNameNotMatchThenCustomHandlerRespondsWithForbidden ()
1241
+ throws Exception {
1242
+ this .spring
1243
+ .register (WebMvcMethodSecurityConfig .class , BasicController .class , BasicService .class ,
1244
+ BasicControllerAdvice .class )
1245
+ .autowire ();
1246
+ // @formatter:off
1247
+ MockHttpServletRequestBuilder requestWithUser = get ("/greetings/authorized-person" )
1248
+ .param ("name" , "john" )
1249
+ .with (user ("rob" ));
1250
+ // @formatter:on
1251
+ MvcResult mvcResult = this .mvc .perform (requestWithUser ).andExpect (status ().isForbidden ()).andReturn ();
1252
+ assertThat (mvcResult .getResponse ().getContentAsString ()).isEqualTo ("""
1253
+ {"message":"Access Denied"}\
1254
+ """ );
1255
+ }
1256
+
1257
+ @ Test
1258
+ void getWhenCustomAdvisorAuthenticationNameMatchesThenRespondsWithOk () throws Exception {
1259
+ this .spring .register (WebMvcMethodSecurityCustomAdvisorConfig .class , BasicController .class ).autowire ();
1260
+ // @formatter:off
1261
+ MockHttpServletRequestBuilder requestWithUser = get ("/authorized-person" )
1262
+ .param ("name" , "rob" )
1263
+ .with (user ("rob" ));
1264
+ // @formatter:on
1265
+ this .mvc .perform (requestWithUser ).andExpect (status ().isOk ());
1266
+ }
1267
+
1268
+ @ Test
1269
+ void getWhenCustomAdvisorAuthenticationNameNotMatchThenRespondsWithForbidden () throws Exception {
1270
+ this .spring .register (WebMvcMethodSecurityCustomAdvisorConfig .class , BasicController .class ).autowire ();
1271
+ // @formatter:off
1272
+ MockHttpServletRequestBuilder requestWithUser = get ("/authorized-person" )
1273
+ .param ("name" , "john" )
1274
+ .with (user ("rob" ));
1275
+ // @formatter:on
1276
+ this .mvc .perform (requestWithUser ).andExpect (status ().isForbidden ());
1277
+ }
1278
+
1184
1279
private static Consumer <ConfigurableWebApplicationContext > disallowBeanOverriding () {
1185
1280
return (context ) -> ((AnnotationConfigWebApplicationContext ) context ).setAllowBeanDefinitionOverriding (false );
1186
1281
}
@@ -1919,4 +2014,106 @@ void onRequestDenied(AuthorizationDeniedEvent<? extends MethodInvocation> denied
1919
2014
1920
2015
}
1921
2016
2017
+ @ EnableWebMvc
2018
+ @ EnableWebSecurity
2019
+ @ EnableMethodSecurity
2020
+ static class WebMvcMethodSecurityConfig {
2021
+
2022
+ }
2023
+
2024
+ @ EnableWebMvc
2025
+ @ EnableWebSecurity
2026
+ @ EnableMethodSecurity
2027
+ static class WebMvcMethodSecurityCustomAdvisorConfig {
2028
+
2029
+ @ Bean
2030
+ AuthorizationAdvisor customAdvisor (SecurityContextHolderStrategy strategy ) {
2031
+ JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut ();
2032
+ pointcut .setPattern (".*AuthorizedPerson.*getName" );
2033
+ return new AuthorizationAdvisor () {
2034
+ @ Override
2035
+ public Object invoke (MethodInvocation mi ) throws Throwable {
2036
+ Authentication auth = strategy .getContext ().getAuthentication ();
2037
+ Object result = mi .proceed ();
2038
+ if (auth .getName ().equals (result )) {
2039
+ return result ;
2040
+ }
2041
+ throw new AccessDeniedException ("Access Denied for User '" + auth .getName () + "'" );
2042
+ }
2043
+
2044
+ @ Override
2045
+ public Pointcut getPointcut () {
2046
+ return pointcut ;
2047
+ }
2048
+
2049
+ @ Override
2050
+ public Advice getAdvice () {
2051
+ return this ;
2052
+ }
2053
+
2054
+ @ Override
2055
+ public int getOrder () {
2056
+ return AuthorizationInterceptorsOrder .POST_FILTER .getOrder () + 1 ;
2057
+ }
2058
+ };
2059
+ }
2060
+
2061
+ }
2062
+
2063
+ @ RestController
2064
+ static class BasicController {
2065
+
2066
+ @ Autowired (required = false )
2067
+ BasicService service ;
2068
+
2069
+ @ GetMapping ("/greetings/authorized-person" )
2070
+ String getAuthorizedPersonGreeting (@ RequestParam String name ) {
2071
+ AuthorizedPerson authorizedPerson = this .service .getAuthorizedPerson (name );
2072
+ return "Hello: " + authorizedPerson .getName ();
2073
+ }
2074
+
2075
+ @ AuthorizeReturnObject
2076
+ @ GetMapping (value = "/authorized-person" , produces = MediaType .APPLICATION_JSON_VALUE )
2077
+ AuthorizedPerson getAuthorizedPerson (@ RequestParam String name ) {
2078
+ return new AuthorizedPerson (name );
2079
+ }
2080
+
2081
+ }
2082
+
2083
+ @ ControllerAdvice
2084
+ static class BasicControllerAdvice {
2085
+
2086
+ @ ExceptionHandler (AccessDeniedException .class )
2087
+ ResponseEntity <Map <String , String >> handleAccessDenied (AccessDeniedException ex ) {
2088
+ Map <String , String > responseBody = Map .of ("message" , ex .getMessage ());
2089
+ return ResponseEntity .status (HttpStatus .FORBIDDEN ).body (responseBody );
2090
+ }
2091
+
2092
+ }
2093
+
2094
+ @ Service
2095
+ static class BasicService {
2096
+
2097
+ @ AuthorizeReturnObject
2098
+ AuthorizedPerson getAuthorizedPerson (String name ) {
2099
+ return new AuthorizedPerson (name );
2100
+ }
2101
+
2102
+ }
2103
+
2104
+ public static class AuthorizedPerson {
2105
+
2106
+ final String name ;
2107
+
2108
+ AuthorizedPerson (String name ) {
2109
+ this .name = name ;
2110
+ }
2111
+
2112
+ @ PostAuthorize ("returnObject == authentication.name" )
2113
+ public String getName () {
2114
+ return this .name ;
2115
+ }
2116
+
2117
+ }
2118
+
1922
2119
}
0 commit comments