Skip to content

Commit 2b33f6f

Browse files
committed
Add Config Tests for AuthenticationPrincipal Templates
Issue gh-15286
1 parent e40c98e commit 2b33f6f

File tree

6 files changed

+218
-12
lines changed

6 files changed

+218
-12
lines changed

config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ static class MessageSecurityPostProcessor implements BeanDefinitionRegistryPostP
305305

306306
private static final String CUSTOM_ARG_RESOLVERS_PROP = "customArgumentResolvers";
307307

308-
private static final String TEMPLATE_EXPRESSION_BEAN_ID = "templateDefaults";
308+
private static final String TEMPLATE_EXPRESSION_BEAN_ID = "annotationExpressionTemplateDefaults";
309309

310310
private final String inboundSecurityInterceptorId;
311311

@@ -333,7 +333,7 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t
333333
AuthenticationPrincipalArgumentResolver.class);
334334
if (registry.containsBeanDefinition(TEMPLATE_EXPRESSION_BEAN_ID)) {
335335
beanDefinition.getPropertyValues()
336-
.add(TEMPLATE_EXPRESSION_BEAN_ID, new RuntimeBeanReference(TEMPLATE_EXPRESSION_BEAN_ID));
336+
.add("templateDefaults", new RuntimeBeanReference(TEMPLATE_EXPRESSION_BEAN_ID));
337337
}
338338
argResolvers.add(beanDefinition);
339339
bd.getPropertyValues().add(CUSTOM_ARG_RESOLVERS_PROP, argResolvers);

config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfigurationTests.java

+43
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
package org.springframework.security.config.annotation.web.configuration;
1818

19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
1924
import org.junit.jupiter.api.AfterEach;
2025
import org.junit.jupiter.api.BeforeEach;
2126
import org.junit.jupiter.api.Test;
@@ -26,6 +31,7 @@
2631
import org.springframework.context.annotation.Configuration;
2732
import org.springframework.security.authentication.TestingAuthenticationToken;
2833
import org.springframework.security.core.Authentication;
34+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
2935
import org.springframework.security.core.annotation.AuthenticationPrincipal;
3036
import org.springframework.security.core.authority.AuthorityUtils;
3137
import org.springframework.security.core.context.SecurityContextHolder;
@@ -39,12 +45,15 @@
3945
import org.springframework.test.web.servlet.ResultMatcher;
4046
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
4147
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
48+
import org.springframework.web.bind.annotation.GetMapping;
4249
import org.springframework.web.bind.annotation.RequestMapping;
50+
import org.springframework.web.bind.annotation.ResponseBody;
4351
import org.springframework.web.context.WebApplicationContext;
4452
import org.springframework.web.servlet.ModelAndView;
4553
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
4654

4755
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
56+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
4857
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
4958
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
5059

@@ -97,10 +106,28 @@ public void csrfToken() throws Exception {
97106
this.mockMvc.perform(request).andExpect(assertResult(csrfToken));
98107
}
99108

109+
@Test
110+
public void metaAnnotationWhenTemplateDefaultsBeanThenResolvesExpression() throws Exception {
111+
this.mockMvc.perform(get("/hi")).andExpect(content().string("Hi, Stranger!"));
112+
Authentication harold = new TestingAuthenticationToken("harold", "password",
113+
AuthorityUtils.createAuthorityList("ROLE_USER"));
114+
SecurityContextHolder.getContext().setAuthentication(harold);
115+
this.mockMvc.perform(get("/hi")).andExpect(content().string("Hi, Harold!"));
116+
}
117+
100118
private ResultMatcher assertResult(Object expected) {
101119
return model().attribute("result", expected);
102120
}
103121

122+
@Retention(RetentionPolicy.RUNTIME)
123+
@Target(ElementType.PARAMETER)
124+
@AuthenticationPrincipal(expression = "#this.equals('{value}')")
125+
@interface IsUser {
126+
127+
String value() default "user";
128+
129+
}
130+
104131
@Controller
105132
static class TestController {
106133

@@ -120,6 +147,17 @@ ModelAndView csrf(CsrfToken token) {
120147
return new ModelAndView("view", "result", token);
121148
}
122149

150+
@GetMapping("/hi")
151+
@ResponseBody
152+
String ifUser(@IsUser("harold") boolean isHarold) {
153+
if (isHarold) {
154+
return "Hi, Harold!";
155+
}
156+
else {
157+
return "Hi, Stranger!";
158+
}
159+
}
160+
123161
}
124162

125163
@Configuration
@@ -132,6 +170,11 @@ TestController testController() {
132170
return new TestController();
133171
}
134172

173+
@Bean
174+
AnnotationTemplateExpressionDefaults templateExpressionDefaults() {
175+
return new AnnotationTemplateExpressionDefaults();
176+
}
177+
135178
}
136179

137180
}

config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java

+95-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.security.config.annotation.web.reactive;
1818

19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
1923
import java.net.URI;
2024

2125
import org.junit.jupiter.api.Test;
@@ -26,6 +30,7 @@
2630
import org.springframework.context.ApplicationContext;
2731
import org.springframework.context.annotation.Bean;
2832
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.security.authentication.TestingAuthenticationToken;
2934
import org.springframework.security.authentication.password.CompromisedPasswordDecision;
3035
import org.springframework.security.authentication.password.CompromisedPasswordException;
3136
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
@@ -34,21 +39,29 @@
3439
import org.springframework.security.config.test.SpringTestContextExtension;
3540
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
3641
import org.springframework.security.config.web.server.ServerHttpSecurity;
42+
import org.springframework.security.core.Authentication;
43+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
44+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
3745
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
3846
import org.springframework.security.core.userdetails.PasswordEncodedUser;
47+
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
3948
import org.springframework.security.core.userdetails.User;
4049
import org.springframework.security.core.userdetails.UserDetails;
4150
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
4251
import org.springframework.security.web.server.SecurityWebFilterChain;
4352
import org.springframework.test.web.reactive.server.WebTestClient;
4453
import org.springframework.util.LinkedMultiValueMap;
4554
import org.springframework.util.MultiValueMap;
55+
import org.springframework.web.bind.annotation.GetMapping;
56+
import org.springframework.web.bind.annotation.RestController;
4657
import org.springframework.web.reactive.config.EnableWebFlux;
4758
import org.springframework.web.reactive.function.BodyInserters;
4859
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
4960

5061
import static org.assertj.core.api.Assertions.assertThat;
5162
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
63+
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication;
64+
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
5265

5366
/**
5467
* Tests for {@link ServerHttpSecurityConfiguration}.
@@ -67,7 +80,10 @@ void setup(ApplicationContext context) {
6780
if (!context.containsBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME)) {
6881
return;
6982
}
70-
this.webClient = WebTestClient.bindToApplicationContext(context).configureClient().build();
83+
this.webClient = WebTestClient.bindToApplicationContext(context)
84+
.apply(springSecurity())
85+
.configureClient()
86+
.build();
7187
}
7288

7389
@Test
@@ -146,6 +162,27 @@ void loginWhenCompromisedPasswordAndRedirectIfPasswordExceptionThenRedirectedToR
146162
// @formatter:on
147163
}
148164

165+
@Test
166+
public void metaAnnotationWhenTemplateDefaultsBeanThenResolvesExpression() throws Exception {
167+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
168+
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
169+
this.webClient.mutateWith(mockAuthentication(user))
170+
.get()
171+
.uri("/hi")
172+
.exchange()
173+
.expectStatus()
174+
.isOk()
175+
.expectBody(String.class)
176+
.isEqualTo("Hi, Stranger!");
177+
Authentication harold = new TestingAuthenticationToken("harold", "password", "ROLE_USER");
178+
this.webClient.mutateWith(mockAuthentication(harold))
179+
.get()
180+
.uri("/hi")
181+
.exchange()
182+
.expectBody(String.class)
183+
.isEqualTo("Hi, Harold!");
184+
}
185+
149186
@Configuration
150187
static class SubclassConfig extends ServerHttpSecurityConfiguration {
151188

@@ -237,4 +274,61 @@ public Mono<CompromisedPasswordDecision> check(String password) {
237274

238275
}
239276

277+
@Retention(RetentionPolicy.RUNTIME)
278+
@Target(ElementType.PARAMETER)
279+
@AuthenticationPrincipal(expression = "#this.equals('{value}')")
280+
@interface IsUser {
281+
282+
String value() default "user";
283+
284+
}
285+
286+
@RestController
287+
static class TestController {
288+
289+
@GetMapping("/hi")
290+
String ifUser(@IsUser("harold") boolean isHarold) {
291+
if (isHarold) {
292+
return "Hi, Harold!";
293+
}
294+
else {
295+
return "Hi, Stranger!";
296+
}
297+
}
298+
299+
}
300+
301+
@Configuration
302+
@EnableWebFlux
303+
@EnableWebFluxSecurity
304+
static class MetaAnnotationPlaceholderConfig {
305+
306+
@Bean
307+
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
308+
// @formatter:off
309+
http
310+
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
311+
.httpBasic(Customizer.withDefaults());
312+
// @formatter:on
313+
return http.build();
314+
}
315+
316+
@Bean
317+
ReactiveUserDetailsService userDetailsService() {
318+
return new MapReactiveUserDetailsService(
319+
User.withUsername("user").password("password").authorities("app").build());
320+
}
321+
322+
@Bean
323+
TestController testController() {
324+
return new TestController();
325+
}
326+
327+
@Bean
328+
AnnotationTemplateExpressionDefaults templateExpressionDefaults() {
329+
return new AnnotationTemplateExpressionDefaults();
330+
}
331+
332+
}
333+
240334
}

config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationTests.java

+37-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.security.config.annotation.web.socket;
1818

19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
1923
import java.util.HashMap;
2024
import java.util.List;
2125
import java.util.Map;
@@ -59,6 +63,7 @@
5963
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
6064
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
6165
import org.springframework.security.core.Authentication;
66+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
6267
import org.springframework.security.core.annotation.AuthenticationPrincipal;
6368
import org.springframework.security.core.authority.AuthorityUtils;
6469
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -164,6 +169,17 @@ public void addsAuthenticationPrincipalResolverWhenNoAuthorization() {
164169
.isEqualTo((String) this.messageUser.getPrincipal());
165170
}
166171

172+
@Test
173+
public void sendMessageWhenMetaAnnotationThenParsesExpression() {
174+
loadConfig(NoInboundSecurityConfig.class);
175+
this.messageUser = new TestingAuthenticationToken("harold", "password", "ROLE_USER");
176+
clientInboundChannel().send(message("/permitAll/hi"));
177+
assertThat(this.context.getBean(MyController.class).message).isEqualTo("Hi, Harold!");
178+
this.messageUser = new TestingAuthenticationToken("user", "password", "ROLE_USER");
179+
clientInboundChannel().send(message("/permitAll/hi"));
180+
assertThat(this.context.getBean(MyController.class).message).isEqualTo("Hi, Stranger!");
181+
}
182+
167183
@Test
168184
public void addsCsrfProtectionWhenNoAuthorization() {
169185
loadConfig(NoInboundSecurityConfig.class);
@@ -365,15 +381,6 @@ public void sendMessageWhenAnonymousConfiguredAndAnonymousUserThenPasses() {
365381
clientInboundChannel().send(message("/anonymous"));
366382
}
367383

368-
@Test
369-
public void sendMessageWhenAnonymousConfiguredAndLoggedInUserThenAccessDeniedException() {
370-
loadConfig(WebSocketSecurityConfig.class);
371-
assertThatExceptionOfType(MessageDeliveryException.class)
372-
.isThrownBy(() -> clientInboundChannel().send(message("/anonymous")))
373-
.withCauseInstanceOf(AccessDeniedException.class);
374-
375-
}
376-
377384
private void assertHandshake(HttpServletRequest request) {
378385
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
379386
assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token);
@@ -585,13 +592,24 @@ TestHandshakeHandler testHandshakeHandler() {
585592

586593
}
587594

595+
@Retention(RetentionPolicy.RUNTIME)
596+
@Target(ElementType.PARAMETER)
597+
@AuthenticationPrincipal(expression = "#this.equals('{value}')")
598+
@interface IsUser {
599+
600+
String value() default "user";
601+
602+
}
603+
588604
@Controller
589605
static class MyController {
590606

591607
String authenticationPrincipal;
592608

593609
MyCustomArgument myCustomArgument;
594610

611+
String message;
612+
595613
@MessageMapping("/authentication")
596614
void authentication(@AuthenticationPrincipal String un) {
597615
this.authenticationPrincipal = un;
@@ -602,6 +620,11 @@ void myCustom(MyCustomArgument myCustomArgument) {
602620
this.myCustomArgument = myCustomArgument;
603621
}
604622

623+
@MessageMapping("/hi")
624+
void sayHello(@IsUser("harold") boolean isHarold) {
625+
this.message = isHarold ? "Hi, Harold!" : "Hi, Stranger!";
626+
}
627+
605628
}
606629

607630
static class MyCustomArgument {
@@ -735,6 +758,11 @@ MyController myController() {
735758
return new MyController();
736759
}
737760

761+
@Bean
762+
AnnotationTemplateExpressionDefaults templateExpressionDefaults() {
763+
return new AnnotationTemplateExpressionDefaults();
764+
}
765+
738766
}
739767

740768
@Configuration

0 commit comments

Comments
 (0)