Просмотр исходного кода

Pick up AuthorizationManager Bean

Closes gh-11067
Closes gh-11068
Josh Cummings 3 лет назад
Родитель
Сommit
32b83aae63

+ 14 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -21,6 +21,8 @@ import java.util.List;
 import javax.servlet.http.HttpServletRequest;
 
 import org.springframework.context.ApplicationContext;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.core.ResolvableType;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
 import org.springframework.security.authorization.AuthorityAuthorizationManager;
@@ -52,6 +54,10 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 	static final AuthorizationManager<RequestAuthorizationContext> permitAllAuthorizationManager = (a,
 			o) -> new AuthorizationDecision(true);
 
+	static final ResolvableType REQUEST_AUTHORIZATION_MANAGER_TYPE = ResolvableType
+			.forType(new ParameterizedTypeReference<AuthorizationManager<HttpServletRequest>>() {
+			});
+
 	private final AuthorizationManagerRequestMatcherRegistry registry;
 
 	private final AuthorizationEventPublisher publisher;
@@ -137,9 +143,15 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 			Assert.state(this.unmappedMatchers == null,
 					() -> "An incomplete mapping was found for " + this.unmappedMatchers
 							+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
-			Assert.state(this.mappingCount > 0,
+			if (this.mappingCount > 0) {
+				return postProcess(this.managerBuilder.build());
+			}
+			if (this.getApplicationContext().getBeanNamesForType(REQUEST_AUTHORIZATION_MANAGER_TYPE).length > 0) {
+				return (AuthorizationManager<HttpServletRequest>) this.getApplicationContext()
+						.getBeanProvider(REQUEST_AUTHORIZATION_MANAGER_TYPE).getObject();
+			}
+			throw new IllegalStateException(
 					"At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())");
-			return postProcess(this.managerBuilder.build());
 		}
 
 		@Override

+ 23 - 2
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -37,6 +37,7 @@ import reactor.util.context.Context;
 import org.springframework.beans.BeansException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.Ordered;
+import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.core.ResolvableType;
 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
 import org.springframework.core.convert.converter.Converter;
@@ -255,6 +256,10 @@ import org.springframework.web.server.WebFilterChain;
  */
 public class ServerHttpSecurity {
 
+	private static final ResolvableType REQUEST_AUTHORIZATION_MANAGER_TYPE = ResolvableType
+			.forType(new ParameterizedTypeReference<ReactiveAuthorizationManager<ServerWebExchange>>() {
+			});
+
 	private ServerWebExchangeMatcher securityMatcher = ServerWebExchangeMatchers.anyExchange();
 
 	private AuthorizeExchangeSpec authorizeExchange;
@@ -1584,6 +1589,8 @@ public class ServerHttpSecurity {
 
 		private boolean anyExchangeRegistered;
 
+		private boolean mappingRegistered;
+
 		/**
 		 * Allows method chaining to continue configuring the {@link ServerHttpSecurity}
 		 * @return the {@link ServerHttpSecurity} to continue configuring
@@ -1616,10 +1623,23 @@ public class ServerHttpSecurity {
 		protected void configure(ServerHttpSecurity http) {
 			Assert.state(this.matcher == null,
 					() -> "The matcher " + this.matcher + " does not have an access rule defined");
-			AuthorizationWebFilter result = new AuthorizationWebFilter(this.managerBldr.build());
+			AuthorizationWebFilter result = new AuthorizationWebFilter(authorizationManager());
 			http.addFilterAt(result, SecurityWebFiltersOrder.AUTHORIZATION);
 		}
 
+		private ReactiveAuthorizationManager<ServerWebExchange> authorizationManager() {
+			if (this.mappingRegistered) {
+				return this.managerBldr.build();
+			}
+			ReactiveAuthorizationManager<ServerWebExchange> anyExchange = getBeanOrNull(
+					REQUEST_AUTHORIZATION_MANAGER_TYPE);
+			if (anyExchange != null) {
+				return anyExchange;
+			}
+			throw new IllegalStateException(
+					"At least one mapping is required (for example, authorizeExchange().anyExchange().authenticated())");
+		}
+
 		/**
 		 * Configures the access for a particular set of exchanges.
 		 */
@@ -1710,6 +1730,7 @@ public class ServerHttpSecurity {
 				AuthorizeExchangeSpec.this.managerBldr
 						.add(new ServerWebExchangeMatcherEntry<>(AuthorizeExchangeSpec.this.matcher, manager));
 				AuthorizeExchangeSpec.this.matcher = null;
+				AuthorizeExchangeSpec.this.mappingRegistered = true;
 				return AuthorizeExchangeSpec.this;
 			}
 

+ 36 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

@@ -20,6 +20,7 @@ import java.util.function.Supplier;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.aopalliance.intercept.MethodInvocation;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -49,10 +50,12 @@ import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.springframework.security.config.Customizer.withDefaults;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -396,6 +399,20 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		this.mvc.perform(requestWithUser).andExpect(status().isOk());
 	}
 
+	@Test
+	public void getWhenOnlyAuthorizationManagerBeanThenRespondsWithOk() throws Exception {
+		this.spring.register(NoRequestsConfig.class, AuthorizationManagerConfig.class, BasicController.class)
+				.autowire();
+		AuthorizationManager<HttpServletRequest> request = (AuthorizationManager<HttpServletRequest>) this.spring
+				.getContext().getBean("request");
+		given(request.check(any(), any())).willReturn(new AuthorizationDecision(true));
+		this.mvc.perform(get("/")).andExpect(status().isOk());
+		verify(request).check(any(), any());
+		AuthorizationManager<MethodInvocation> method = (AuthorizationManager<MethodInvocation>) this.spring
+				.getContext().getBean("method");
+		verifyNoInteractions(method);
+	}
+
 	@EnableWebSecurity
 	static class NoRequestsConfig {
 
@@ -726,6 +743,25 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 	}
 
+	@Configuration
+	static class AuthorizationManagerConfig {
+
+		private final AuthorizationManager<HttpServletRequest> request = mock(AuthorizationManager.class);
+
+		private final AuthorizationManager<MethodInvocation> method = mock(AuthorizationManager.class);
+
+		@Bean
+		AuthorizationManager<HttpServletRequest> request() {
+			return this.request;
+		}
+
+		@Bean
+		AuthorizationManager<MethodInvocation> method() {
+			return this.method;
+		}
+
+	}
+
 	@RestController
 	static class BasicController {
 

+ 72 - 1
config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -16,14 +16,29 @@
 
 package org.springframework.security.config.web.server;
 
+import org.aopalliance.intercept.MethodInvocation;
 import org.junit.jupiter.api.Test;
+import reactor.core.publisher.Mono;
 
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
+import org.springframework.security.authorization.ReactiveAuthorizationManager;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
 import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
+import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
+import org.springframework.security.web.server.SecurityWebFilterChain;
 import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.web.server.ServerWebExchange;
 
 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.springframework.security.config.Customizer.withDefaults;
 
 /**
  * @author Rob Winch
@@ -33,6 +48,8 @@ public class AuthorizeExchangeSpecTests {
 
 	ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication();
 
+	public final SpringTestContext spring = new SpringTestContext(this);
+
 	@Test
 	public void antMatchersWhenMethodAndPatternsThenDiscriminatesByMethod() {
 		this.http.csrf().disable().authorizeExchange().pathMatchers(HttpMethod.POST, "/a", "/b").denyAll().anyExchange()
@@ -107,6 +124,26 @@ public class AuthorizeExchangeSpecTests {
 		// @formatter:on
 	}
 
+	@Test
+	public void buildWhenAuthorizationManagerThenWorks() {
+		this.spring.register(NoRequestsConfig.class, AuthorizationManagerConfig.class).autowire();
+		ReactiveAuthorizationManager<ServerWebExchange> request = (ReactiveAuthorizationManager<ServerWebExchange>) this.spring
+				.getContext().getBean("request");
+		given(request.verify(any(), any())).willReturn(Mono.empty());
+		SecurityWebFilterChain filterChain = this.spring.getContext().getBean(SecurityWebFilterChain.class);
+		WebTestClient client = WebTestClientBuilder.bindToWebFilters(filterChain).build();
+		// @formatter:off
+		client.get()
+				.uri("/a")
+				.exchange()
+				.expectStatus().isOk();
+		// @formatter:on
+		verify(request).verify(any(), any());
+		ReactiveAuthorizationManager<MethodInvocation> method = (ReactiveAuthorizationManager<MethodInvocation>) this.spring
+				.getContext().getBean("method");
+		verifyNoInteractions(method);
+	}
+
 	@Test
 	public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() {
 		this.http.authorizeExchange().pathMatchers("/incomplete");
@@ -141,4 +178,38 @@ public class AuthorizeExchangeSpecTests {
 		return WebTestClientBuilder.bindToWebFilters(this.http.build()).build();
 	}
 
+	@EnableWebFluxSecurity
+	static class NoRequestsConfig {
+
+		@Bean
+		SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
+			// @formatter:off
+			return http
+					.authorizeExchange(withDefaults())
+					.build();
+			// @formatter:on
+		}
+
+	}
+
+	@Configuration
+	static class AuthorizationManagerConfig {
+
+		private final ReactiveAuthorizationManager<ServerWebExchange> request = mock(
+				ReactiveAuthorizationManager.class);
+
+		private final ReactiveAuthorizationManager<MethodInvocation> method = mock(ReactiveAuthorizationManager.class);
+
+		@Bean
+		ReactiveAuthorizationManager<ServerWebExchange> request() {
+			return this.request;
+		}
+
+		@Bean
+		ReactiveAuthorizationManager<MethodInvocation> method() {
+			return this.method;
+		}
+
+	}
+
 }