Przeglądaj źródła

Add remaining methods from ExpressionUrlAuthorizationConfigurer to AuthorizeHttpRequestsConfigurer

- Added fullyAuthenticated
- Added rememberMe
- Added anonymous

Closes gh-11360
Evgeniy Cheban 3 lat temu
rodzic
commit
c4b0e9bd74

+ 33 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -317,6 +317,39 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 			return access(AuthenticatedAuthorizationManager.authenticated());
 		}
 
+		/**
+		 * Specify that URLs are allowed by users who have authenticated and were not
+		 * "remembered".
+		 * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
+		 * customization
+		 * @since 5.8
+		 * @see RememberMeConfigurer
+		 */
+		public AuthorizationManagerRequestMatcherRegistry fullyAuthenticated() {
+			return access(AuthenticatedAuthorizationManager.fullyAuthenticated());
+		}
+
+		/**
+		 * Specify that URLs are allowed by users that have been remembered.
+		 * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
+		 * customization
+		 * @since 5.8
+		 * @see RememberMeConfigurer
+		 */
+		public AuthorizationManagerRequestMatcherRegistry rememberMe() {
+			return access(AuthenticatedAuthorizationManager.rememberMe());
+		}
+
+		/**
+		 * Specify that URLs are allowed by anonymous users.
+		 * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
+		 * customization
+		 * @since 5.8
+		 */
+		public AuthorizationManagerRequestMatcherRegistry anonymous() {
+			return access(AuthenticatedAuthorizationManager.anonymous());
+		}
+
 		/**
 		 * Allows specifying a custom {@link AuthorizationManager}.
 		 * @param manager the {@link AuthorizationManager} to use

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

@@ -26,6 +26,8 @@ import org.springframework.beans.factory.BeanCreationException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.RememberMeAuthenticationToken;
+import org.springframework.security.authentication.TestAuthentication;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationManager;
@@ -35,7 +37,10 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
 import org.springframework.security.web.access.intercept.AuthorizationFilter;
@@ -57,6 +62,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.springframework.security.config.Customizer.withDefaults;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -492,6 +498,50 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		};
 	}
 
+	@Test
+	public void getWhenFullyAuthenticatedConfiguredAndRememberMeTokenThenRespondsWithUnauthorized() throws Exception {
+		this.spring.register(FullyAuthenticatedConfig.class, BasicController.class).autowire();
+		RememberMeAuthenticationToken rememberMe = new RememberMeAuthenticationToken("key", "user",
+				AuthorityUtils.createAuthorityList("ROLE_USER"));
+		MockHttpServletRequestBuilder requestWithRememberMe = get("/").with(authentication(rememberMe));
+		this.mvc.perform(requestWithRememberMe).andExpect(status().isUnauthorized());
+	}
+
+	@Test
+	public void getWhenFullyAuthenticatedConfiguredAndUserThenRespondsWithOk() throws Exception {
+		this.spring.register(FullyAuthenticatedConfig.class, BasicController.class).autowire();
+		MockHttpServletRequestBuilder requestWithUser = get("/").with(user("user").roles("USER"));
+		this.mvc.perform(requestWithUser).andExpect(status().isOk());
+	}
+
+	@Test
+	public void getWhenRememberMeConfiguredAndNoUserThenRespondsWithUnauthorized() throws Exception {
+		this.spring.register(RememberMeConfig.class, BasicController.class).autowire();
+		this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
+	}
+
+	@Test
+	public void getWhenRememberMeConfiguredAndRememberMeTokenThenRespondsWithOk() throws Exception {
+		this.spring.register(RememberMeConfig.class, BasicController.class).autowire();
+		RememberMeAuthenticationToken rememberMe = new RememberMeAuthenticationToken("key", "user",
+				AuthorityUtils.createAuthorityList("ROLE_USER"));
+		MockHttpServletRequestBuilder requestWithRememberMe = get("/").with(authentication(rememberMe));
+		this.mvc.perform(requestWithRememberMe).andExpect(status().isOk());
+	}
+
+	@Test
+	public void getWhenAnonymousConfiguredAndAnonymousUserThenRespondsWithOk() throws Exception {
+		this.spring.register(AnonymousConfig.class, BasicController.class).autowire();
+		this.mvc.perform(get("/")).andExpect(status().isOk());
+	}
+
+	@Test
+	public void getWhenAnonymousConfiguredAndLoggedInUserThenRespondsWithForbidden() throws Exception {
+		this.spring.register(AnonymousConfig.class, BasicController.class).autowire();
+		MockHttpServletRequestBuilder requestWithUser = get("/").with(user("user"));
+		this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
+	}
+
 	@EnableWebSecurity
 	static class NoRequestsConfig {
 
@@ -887,6 +937,74 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 	}
 
+	@EnableWebSecurity
+	static class FullyAuthenticatedConfig {
+
+		@Bean
+		SecurityFilterChain chain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+					.httpBasic()
+					.and()
+					.rememberMe()
+					.and()
+					.authorizeHttpRequests((requests) -> requests
+							.anyRequest().fullyAuthenticated()
+					);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		UserDetailsService userDetailsService() {
+			return new InMemoryUserDetailsManager(TestAuthentication.user());
+		}
+
+	}
+
+	@EnableWebSecurity
+	static class RememberMeConfig {
+
+		@Bean
+		SecurityFilterChain chain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+					.httpBasic()
+					.and()
+					.rememberMe()
+					.and()
+					.authorizeHttpRequests((requests) -> requests
+							.anyRequest().rememberMe()
+					);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		UserDetailsService userDetailsService() {
+			return new InMemoryUserDetailsManager(TestAuthentication.user());
+		}
+
+	}
+
+	@EnableWebSecurity
+	static class AnonymousConfig {
+
+		@Bean
+		SecurityFilterChain chain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+					.httpBasic()
+					.and()
+					.authorizeHttpRequests((requests) -> requests
+							.anyRequest().anonymous()
+					);
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
 	@Configuration
 	static class AuthorizationEventPublisherConfig {
 

+ 112 - 9
core/src/main/java/org/springframework/security/authorization/AuthenticatedAuthorizationManager.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 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.
@@ -21,6 +21,7 @@ import java.util.function.Supplier;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
 
 /**
  * An {@link AuthorizationManager} that determines if the current user is authenticated.
@@ -31,7 +32,35 @@ import org.springframework.security.core.Authentication;
  */
 public final class AuthenticatedAuthorizationManager<T> implements AuthorizationManager<T> {
 
-	private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+	private final AbstractAuthorizationStrategy authorizationStrategy;
+
+	/**
+	 * Creates an instance that determines if the current user is authenticated, this is
+	 * the same as calling {@link #authenticated()} factory method.
+	 *
+	 * @since 5.8
+	 * @see #authenticated()
+	 * @see #fullyAuthenticated()
+	 * @see #rememberMe()
+	 * @see #anonymous()
+	 */
+	public AuthenticatedAuthorizationManager() {
+		this(new AuthenticatedAuthorizationStrategy());
+	}
+
+	private AuthenticatedAuthorizationManager(AbstractAuthorizationStrategy authorizationStrategy) {
+		this.authorizationStrategy = authorizationStrategy;
+	}
+
+	/**
+	 * Sets the {@link AuthenticationTrustResolver} to be used. Default is
+	 * {@link AuthenticationTrustResolverImpl}. Cannot be null.
+	 * @param trustResolver the {@link AuthenticationTrustResolver} to use
+	 * @since 5.8
+	 */
+	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
+		this.authorizationStrategy.setTrustResolver(trustResolver);
+	}
 
 	/**
 	 * Creates an instance of {@link AuthenticatedAuthorizationManager}.
@@ -43,24 +72,98 @@ public final class AuthenticatedAuthorizationManager<T> implements Authorization
 	}
 
 	/**
-	 * Determines if the current user is authorized by evaluating if the
-	 * {@link Authentication} is not anonymous and authenticated.
+	 * Creates an instance of {@link AuthenticatedAuthorizationManager} that determines if
+	 * the {@link Authentication} is authenticated without using remember me.
+	 * @param <T> the type of object being authorized
+	 * @return the new instance
+	 * @since 5.8
+	 */
+	public static <T> AuthenticatedAuthorizationManager<T> fullyAuthenticated() {
+		return new AuthenticatedAuthorizationManager<>(new FullyAuthenticatedAuthorizationStrategy());
+	}
+
+	/**
+	 * Creates an instance of {@link AuthenticatedAuthorizationManager} that determines if
+	 * the {@link Authentication} is authenticated using remember me.
+	 * @param <T> the type of object being authorized
+	 * @return the new instance
+	 * @since 5.8
+	 */
+	public static <T> AuthenticatedAuthorizationManager<T> rememberMe() {
+		return new AuthenticatedAuthorizationManager<>(new RememberMeAuthorizationStrategy());
+	}
+
+	/**
+	 * Creates an instance of {@link AuthenticatedAuthorizationManager} that determines if
+	 * the {@link Authentication} is anonymous.
+	 * @param <T> the type of object being authorized
+	 * @return the new instance
+	 * @since 5.8
+	 */
+	public static <T> AuthenticatedAuthorizationManager<T> anonymous() {
+		return new AuthenticatedAuthorizationManager<>(new AnonymousAuthorizationStrategy());
+	}
+
+	/**
+	 * Determines if the current user is authorized according to the given strategy.
 	 * @param authentication the {@link Supplier} of the {@link Authentication} to check
 	 * @param object the {@link T} object to check
 	 * @return an {@link AuthorizationDecision}
 	 */
 	@Override
 	public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
-		boolean granted = isGranted(authentication.get());
+		boolean granted = this.authorizationStrategy.isGranted(authentication.get());
 		return new AuthorizationDecision(granted);
 	}
 
-	private boolean isGranted(Authentication authentication) {
-		return authentication != null && isNotAnonymous(authentication) && authentication.isAuthenticated();
+	private abstract static class AbstractAuthorizationStrategy {
+
+		AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+
+		private void setTrustResolver(AuthenticationTrustResolver trustResolver) {
+			Assert.notNull(trustResolver, "trustResolver cannot be null");
+			this.trustResolver = trustResolver;
+		}
+
+		abstract boolean isGranted(Authentication authentication);
+
 	}
 
-	private boolean isNotAnonymous(Authentication authentication) {
-		return !this.trustResolver.isAnonymous(authentication);
+	private static class AuthenticatedAuthorizationStrategy extends AbstractAuthorizationStrategy {
+
+		@Override
+		boolean isGranted(Authentication authentication) {
+			return authentication != null && !this.trustResolver.isAnonymous(authentication)
+					&& authentication.isAuthenticated();
+		}
+
+	}
+
+	private static final class FullyAuthenticatedAuthorizationStrategy extends AuthenticatedAuthorizationStrategy {
+
+		@Override
+		boolean isGranted(Authentication authentication) {
+			return super.isGranted(authentication) && !this.trustResolver.isRememberMe(authentication);
+		}
+
+	}
+
+	private static final class AnonymousAuthorizationStrategy extends AbstractAuthorizationStrategy {
+
+		@Override
+		boolean isGranted(Authentication authentication) {
+			return this.trustResolver.isAnonymous(authentication);
+		}
+
+	}
+
+	private static final class RememberMeAuthorizationStrategy extends AbstractAuthorizationStrategy {
+
+		@Override
+		boolean isGranted(Authentication authentication) {
+			return this.trustResolver.isRememberMe(authentication);
+		}
+
 	}
 
 }

+ 83 - 1
core/src/test/java/org/springframework/security/authorization/AuthenticatedAuthorizationManagerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 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,11 +16,13 @@
 
 package org.springframework.security.authorization;
 
+import java.util.Collections;
 import java.util.function.Supplier;
 
 import org.junit.jupiter.api.Test;
 
 import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.RememberMeAuthenticationToken;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.authority.AuthorityUtils;
@@ -74,4 +76,84 @@ public class AuthenticatedAuthorizationManagerTests {
 		assertThat(manager.check(() -> authentication, object).isGranted()).isFalse();
 	}
 
+	@Test
+	public void authenticatedWhenUserRememberMeThenGrantedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.authenticated();
+		Supplier<Authentication> authentication = () -> new RememberMeAuthenticationToken("user", "password",
+				Collections.emptyList());
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isTrue();
+	}
+
+	@Test
+	public void fullyAuthenticatedWhenUserNotAnonymousAndNotRememberMeThenGrantedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.fullyAuthenticated();
+		Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN",
+				"ROLE_USER");
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isTrue();
+	}
+
+	@Test
+	public void fullyAuthenticatedWhenUserNullThenDeniedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.fullyAuthenticated();
+		Supplier<Authentication> authentication = () -> null;
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isFalse();
+	}
+
+	@Test
+	public void fullyAuthenticatedWhenUserRememberMeThenDeniedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.fullyAuthenticated();
+		Supplier<Authentication> authentication = () -> new RememberMeAuthenticationToken("user", "password",
+				Collections.emptyList());
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isFalse();
+	}
+
+	@Test
+	public void fullyAuthenticatedWhenUserAnonymousThenDeniedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.fullyAuthenticated();
+		Supplier<Authentication> authentication = () -> new AnonymousAuthenticationToken("key", "principal",
+				AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isFalse();
+	}
+
+	@Test
+	public void anonymousWhenUserAnonymousThenGrantedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.anonymous();
+		Supplier<Authentication> authentication = () -> new AnonymousAuthenticationToken("key", "principal",
+				AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isTrue();
+	}
+
+	@Test
+	public void anonymousWhenUserNotAnonymousThenDeniedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.anonymous();
+		Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN",
+				"ROLE_USER");
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isFalse();
+	}
+
+	@Test
+	public void rememberMeWhenUserRememberMeThenGrantedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.rememberMe();
+		Supplier<Authentication> authentication = () -> new RememberMeAuthenticationToken("user", "password",
+				Collections.emptyList());
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isTrue();
+	}
+
+	@Test
+	public void rememberMeWhenUserNotRememberMeThenDeniedDecision() {
+		AuthenticatedAuthorizationManager<Object> manager = AuthenticatedAuthorizationManager.rememberMe();
+		Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN",
+				"ROLE_USER");
+		Object object = new Object();
+		assertThat(manager.check(authentication, object).isGranted()).isFalse();
+	}
+
 }