فهرست منبع

Add Not Support

Closes gh-14058
Josh Cummings 1 سال پیش
والد
کامیت
bb6b55aca3

+ 17 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -30,6 +30,7 @@ import org.springframework.security.authorization.AuthorityAuthorizationManager;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManagers;
 import org.springframework.security.authorization.ObservationAuthorizationManager;
 import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -244,11 +245,14 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 	 * {@link RequestMatcher}s.
 	 *
 	 * @author Evgeniy Cheban
+	 * @author Josh Cummings
 	 */
 	public class AuthorizedUrl {
 
 		private final List<? extends RequestMatcher> matchers;
 
+		private boolean not;
+
 		/**
 		 * Creates an instance.
 		 * @param matchers the {@link RequestMatcher} instances to map
@@ -261,6 +265,16 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 			return this.matchers;
 		}
 
+		/**
+		 * Negates the following authorization rule.
+		 * @return the {@link AuthorizedUrl} for further customization
+		 * @since 6.3
+		 */
+		public AuthorizedUrl not() {
+			this.not = true;
+			return this;
+		}
+
 		/**
 		 * Specify that URLs are allowed by anyone.
 		 * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
@@ -382,7 +396,9 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		public AuthorizationManagerRequestMatcherRegistry access(
 				AuthorizationManager<RequestAuthorizationContext> manager) {
 			Assert.notNull(manager, "manager cannot be null");
-			return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
+			return (this.not)
+					? AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, AuthorizationManagers.not(manager))
+					: AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
 		}
 
 	}

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

@@ -596,6 +596,20 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
 	}
 
+	@Test
+	public void getWhenNotConfigAndAuthenticatedThenRespondsWithForbidden() throws Exception {
+		this.spring.register(NotConfig.class, BasicController.class).autowire();
+		MockHttpServletRequestBuilder requestWithUser = get("/").with(user("user"));
+		this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
+	}
+
+	@Test
+	public void getWhenNotConfigAndNotAuthenticatedThenRespondsWithOk() throws Exception {
+		this.spring.register(NotConfig.class, BasicController.class).autowire();
+		MockHttpServletRequestBuilder requestWithUser = get("/");
+		this.mvc.perform(requestWithUser).andExpect(status().isOk());
+	}
+
 	@Configuration
 	@EnableWebSecurity
 	static class GrantedAuthorityDefaultHasRoleConfig {
@@ -1136,6 +1150,24 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 	}
 
+	@Configuration
+	@EnableWebSecurity
+	static class NotConfig {
+
+		@Bean
+		SecurityFilterChain chain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.httpBasic(withDefaults())
+				.authorizeHttpRequests((requests) -> requests
+					.anyRequest().not().authenticated()
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
 	@Configuration
 	static class AuthorizationEventPublisherConfig {
 

+ 36 - 0
core/src/main/java/org/springframework/security/authorization/AuthorizationManagers.java

@@ -23,6 +23,7 @@ import java.util.List;
  * A factory class to create an {@link AuthorizationManager} instances.
  *
  * @author Evgeniy Cheban
+ * @author Josh Cummings
  * @since 5.8
  */
 public final class AuthorizationManagers {
@@ -119,6 +120,25 @@ public final class AuthorizationManagers {
 		};
 	}
 
+	/**
+	 * Creates an {@link AuthorizationManager} that reverses whatever decision the given
+	 * {@link AuthorizationManager} granted. If the given {@link AuthorizationManager}
+	 * abstains, then the returned manager also abstains.
+	 * @param <T> the type of object that is being authorized
+	 * @param manager the {@link AuthorizationManager} to reverse
+	 * @return the reversing {@link AuthorizationManager}
+	 * @since 6.3
+	 */
+	public static <T> AuthorizationManager<T> not(AuthorizationManager<T> manager) {
+		return (authentication, object) -> {
+			AuthorizationDecision decision = manager.check(authentication, object);
+			if (decision == null) {
+				return null;
+			}
+			return new NotAuthorizationDecision(decision);
+		};
+	}
+
 	private AuthorizationManagers() {
 	}
 
@@ -138,4 +158,20 @@ public final class AuthorizationManagers {
 
 	}
 
+	private static final class NotAuthorizationDecision extends AuthorizationDecision {
+
+		private final AuthorizationDecision decision;
+
+		private NotAuthorizationDecision(AuthorizationDecision decision) {
+			super(!decision.isGranted());
+			this.decision = decision;
+		}
+
+		@Override
+		public String toString() {
+			return "NotAuthorizationDecision [decision=" + this.decision + ']';
+		}
+
+	}
+
 }

+ 15 - 0
core/src/test/java/org/springframework/security/authorization/AuthorizationManagersTests.java

@@ -224,4 +224,19 @@ class AuthorizationManagersTests {
 		assertThat(decision).isNull();
 	}
 
+	@Test
+	void checkNotWhenEmptyThenAbstainedDecision() {
+		AuthorizationManager<?> negated = AuthorizationManagers.not((a, o) -> null);
+		AuthorizationDecision decision = negated.check(null, null);
+		assertThat(decision).isNull();
+	}
+
+	@Test
+	void checkNotWhenGrantedThenDeniedDecision() {
+		AuthorizationManager<?> negated = AuthorizationManagers.not((a, o) -> new AuthorizationDecision(true));
+		AuthorizationDecision decision = negated.check(null, null);
+		assertThat(decision).isNotNull();
+		assertThat(decision.isGranted()).isFalse();
+	}
+
 }