Skip to content

Commit 4ca5346

Browse files
committed
Pick up AuthorizationManager Bean
Closes gh-11067 Closes gh-11068
1 parent cfb1745 commit 4ca5346

File tree

4 files changed

+145
-5
lines changed

4 files changed

+145
-5
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import jakarta.servlet.http.HttpServletRequest;
2222

2323
import org.springframework.context.ApplicationContext;
24+
import org.springframework.core.ParameterizedTypeReference;
25+
import org.springframework.core.ResolvableType;
2426
import org.springframework.http.HttpMethod;
2527
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
2628
import org.springframework.security.authorization.AuthorityAuthorizationManager;
@@ -52,6 +54,10 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
5254
static final AuthorizationManager<RequestAuthorizationContext> permitAllAuthorizationManager = (a,
5355
o) -> new AuthorizationDecision(true);
5456

57+
static final ResolvableType REQUEST_AUTHORIZATION_MANAGER_TYPE = ResolvableType
58+
.forType(new ParameterizedTypeReference<AuthorizationManager<HttpServletRequest>>() {
59+
});
60+
5561
private final AuthorizationManagerRequestMatcherRegistry registry;
5662

5763
private final AuthorizationEventPublisher publisher;
@@ -137,9 +143,15 @@ private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
137143
Assert.state(this.unmappedMatchers == null,
138144
() -> "An incomplete mapping was found for " + this.unmappedMatchers
139145
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
140-
Assert.state(this.mappingCount > 0,
146+
if (this.mappingCount > 0) {
147+
return postProcess(this.managerBuilder.build());
148+
}
149+
if (this.getApplicationContext().getBeanNamesForType(REQUEST_AUTHORIZATION_MANAGER_TYPE).length > 0) {
150+
return (AuthorizationManager<HttpServletRequest>) this.getApplicationContext()
151+
.getBeanProvider(REQUEST_AUTHORIZATION_MANAGER_TYPE).getObject();
152+
}
153+
throw new IllegalStateException(
141154
"At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())");
142-
return postProcess(this.managerBuilder.build());
143155
}
144156

145157
@Override

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -37,6 +37,7 @@
3737
import org.springframework.beans.BeansException;
3838
import org.springframework.context.ApplicationContext;
3939
import org.springframework.core.Ordered;
40+
import org.springframework.core.ParameterizedTypeReference;
4041
import org.springframework.core.ResolvableType;
4142
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
4243
import org.springframework.core.convert.converter.Converter;
@@ -255,6 +256,10 @@
255256
*/
256257
public class ServerHttpSecurity {
257258

259+
private static final ResolvableType REQUEST_AUTHORIZATION_MANAGER_TYPE = ResolvableType
260+
.forType(new ParameterizedTypeReference<ReactiveAuthorizationManager<ServerWebExchange>>() {
261+
});
262+
258263
private ServerWebExchangeMatcher securityMatcher = ServerWebExchangeMatchers.anyExchange();
259264

260265
private AuthorizeExchangeSpec authorizeExchange;
@@ -1584,6 +1589,8 @@ public class AuthorizeExchangeSpec extends AbstractServerWebExchangeMatcherRegis
15841589

15851590
private boolean anyExchangeRegistered;
15861591

1592+
private boolean mappingRegistered;
1593+
15871594
/**
15881595
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
15891596
* @return the {@link ServerHttpSecurity} to continue configuring
@@ -1616,10 +1623,23 @@ protected Access registerMatcher(ServerWebExchangeMatcher matcher) {
16161623
protected void configure(ServerHttpSecurity http) {
16171624
Assert.state(this.matcher == null,
16181625
() -> "The matcher " + this.matcher + " does not have an access rule defined");
1619-
AuthorizationWebFilter result = new AuthorizationWebFilter(this.managerBldr.build());
1626+
AuthorizationWebFilter result = new AuthorizationWebFilter(authorizationManager());
16201627
http.addFilterAt(result, SecurityWebFiltersOrder.AUTHORIZATION);
16211628
}
16221629

1630+
private ReactiveAuthorizationManager<ServerWebExchange> authorizationManager() {
1631+
if (this.mappingRegistered) {
1632+
return this.managerBldr.build();
1633+
}
1634+
ReactiveAuthorizationManager<ServerWebExchange> anyExchange = getBeanOrNull(
1635+
REQUEST_AUTHORIZATION_MANAGER_TYPE);
1636+
if (anyExchange != null) {
1637+
return anyExchange;
1638+
}
1639+
throw new IllegalStateException(
1640+
"At least one mapping is required (for example, authorizeExchange().anyExchange().authenticated())");
1641+
}
1642+
16231643
/**
16241644
* Configures the access for a particular set of exchanges.
16251645
*/
@@ -1710,6 +1730,7 @@ public AuthorizeExchangeSpec access(ReactiveAuthorizationManager<AuthorizationCo
17101730
AuthorizeExchangeSpec.this.managerBldr
17111731
.add(new ServerWebExchangeMatcherEntry<>(AuthorizeExchangeSpec.this.matcher, manager));
17121732
AuthorizeExchangeSpec.this.matcher = null;
1733+
AuthorizeExchangeSpec.this.mappingRegistered = true;
17131734
return AuthorizeExchangeSpec.this;
17141735
}
17151736

config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.function.Supplier;
2020

2121
import jakarta.servlet.http.HttpServletRequest;
22+
import org.aopalliance.intercept.MethodInvocation;
2223
import org.junit.jupiter.api.Test;
2324
import org.junit.jupiter.api.extension.ExtendWith;
2425

@@ -48,10 +49,12 @@
4849
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
4950

5051
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
52+
import static org.mockito.BDDMockito.given;
5153
import static org.mockito.Mockito.any;
5254
import static org.mockito.Mockito.mock;
5355
import static org.mockito.Mockito.spy;
5456
import static org.mockito.Mockito.verify;
57+
import static org.mockito.Mockito.verifyNoInteractions;
5558
import static org.springframework.security.config.Customizer.withDefaults;
5659
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
5760
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -395,6 +398,20 @@ public void getWhenAnyRequestAuthenticatedConfiguredAndUserLoggedInThenRespondsW
395398
this.mvc.perform(requestWithUser).andExpect(status().isOk());
396399
}
397400

401+
@Test
402+
public void getWhenOnlyAuthorizationManagerBeanThenRespondsWithOk() throws Exception {
403+
this.spring.register(NoRequestsConfig.class, AuthorizationManagerConfig.class, BasicController.class)
404+
.autowire();
405+
AuthorizationManager<HttpServletRequest> request = (AuthorizationManager<HttpServletRequest>) this.spring
406+
.getContext().getBean("request");
407+
given(request.check(any(), any())).willReturn(new AuthorizationDecision(true));
408+
this.mvc.perform(get("/")).andExpect(status().isOk());
409+
verify(request).check(any(), any());
410+
AuthorizationManager<MethodInvocation> method = (AuthorizationManager<MethodInvocation>) this.spring
411+
.getContext().getBean("method");
412+
verifyNoInteractions(method);
413+
}
414+
398415
@EnableWebSecurity
399416
static class NoRequestsConfig {
400417

@@ -725,6 +742,25 @@ AuthorizationEventPublisher authorizationEventPublisher() {
725742

726743
}
727744

745+
@Configuration
746+
static class AuthorizationManagerConfig {
747+
748+
private final AuthorizationManager<HttpServletRequest> request = mock(AuthorizationManager.class);
749+
750+
private final AuthorizationManager<MethodInvocation> method = mock(AuthorizationManager.class);
751+
752+
@Bean
753+
AuthorizationManager<HttpServletRequest> request() {
754+
return this.request;
755+
}
756+
757+
@Bean
758+
AuthorizationManager<MethodInvocation> method() {
759+
return this.method;
760+
}
761+
762+
}
763+
728764
@RestController
729765
static class BasicController {
730766

config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -16,14 +16,29 @@
1616

1717
package org.springframework.security.config.web.server;
1818

19+
import org.aopalliance.intercept.MethodInvocation;
1920
import org.junit.jupiter.api.Test;
21+
import reactor.core.publisher.Mono;
2022

23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
2125
import org.springframework.http.HttpMethod;
26+
import org.springframework.security.authorization.ReactiveAuthorizationManager;
27+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
2228
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
29+
import org.springframework.security.config.test.SpringTestContext;
2330
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
31+
import org.springframework.security.web.server.SecurityWebFilterChain;
2432
import org.springframework.test.web.reactive.server.WebTestClient;
33+
import org.springframework.web.server.ServerWebExchange;
2534

2635
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
36+
import static org.mockito.ArgumentMatchers.any;
37+
import static org.mockito.BDDMockito.given;
38+
import static org.mockito.Mockito.mock;
39+
import static org.mockito.Mockito.verify;
40+
import static org.mockito.Mockito.verifyNoInteractions;
41+
import static org.springframework.security.config.Customizer.withDefaults;
2742

2843
/**
2944
* @author Rob Winch
@@ -33,6 +48,8 @@ public class AuthorizeExchangeSpecTests {
3348

3449
ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication();
3550

51+
public final SpringTestContext spring = new SpringTestContext(this);
52+
3653
@Test
3754
public void antMatchersWhenMethodAndPatternsThenDiscriminatesByMethod() {
3855
this.http.csrf().disable().authorizeExchange().pathMatchers(HttpMethod.POST, "/a", "/b").denyAll().anyExchange()
@@ -107,6 +124,26 @@ public void antMatchersWhenPatternsInLambdaThenAnyMethod() {
107124
// @formatter:on
108125
}
109126

127+
@Test
128+
public void buildWhenAuthorizationManagerThenWorks() {
129+
this.spring.register(NoRequestsConfig.class, AuthorizationManagerConfig.class).autowire();
130+
ReactiveAuthorizationManager<ServerWebExchange> request = (ReactiveAuthorizationManager<ServerWebExchange>) this.spring
131+
.getContext().getBean("request");
132+
given(request.verify(any(), any())).willReturn(Mono.empty());
133+
SecurityWebFilterChain filterChain = this.spring.getContext().getBean(SecurityWebFilterChain.class);
134+
WebTestClient client = WebTestClientBuilder.bindToWebFilters(filterChain).build();
135+
// @formatter:off
136+
client.get()
137+
.uri("/a")
138+
.exchange()
139+
.expectStatus().isOk();
140+
// @formatter:on
141+
verify(request).verify(any(), any());
142+
ReactiveAuthorizationManager<MethodInvocation> method = (ReactiveAuthorizationManager<MethodInvocation>) this.spring
143+
.getContext().getBean("method");
144+
verifyNoInteractions(method);
145+
}
146+
110147
@Test
111148
public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() {
112149
this.http.authorizeExchange().pathMatchers("/incomplete");
@@ -141,4 +178,38 @@ private WebTestClient buildClient() {
141178
return WebTestClientBuilder.bindToWebFilters(this.http.build()).build();
142179
}
143180

181+
@EnableWebFluxSecurity
182+
static class NoRequestsConfig {
183+
184+
@Bean
185+
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
186+
// @formatter:off
187+
return http
188+
.authorizeExchange(withDefaults())
189+
.build();
190+
// @formatter:on
191+
}
192+
193+
}
194+
195+
@Configuration
196+
static class AuthorizationManagerConfig {
197+
198+
private final ReactiveAuthorizationManager<ServerWebExchange> request = mock(
199+
ReactiveAuthorizationManager.class);
200+
201+
private final ReactiveAuthorizationManager<MethodInvocation> method = mock(ReactiveAuthorizationManager.class);
202+
203+
@Bean
204+
ReactiveAuthorizationManager<ServerWebExchange> request() {
205+
return this.request;
206+
}
207+
208+
@Bean
209+
ReactiveAuthorizationManager<MethodInvocation> method() {
210+
return this.method;
211+
}
212+
213+
}
214+
144215
}

0 commit comments

Comments
 (0)