diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java index 7d97ea50d2..58fe15f9f5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java @@ -403,7 +403,7 @@ public class FormLoginConfigurerTests { UserDetails user = PasswordEncodedUser.user(); this.mockMvc.perform(get("/profile").with(user(user))) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); this.mockMvc .perform(post("/ott/generate").param("username", "rod") .with(user(user)) @@ -421,13 +421,13 @@ public class FormLoginConfigurerTests { .build(); this.mockMvc.perform(get("/profile").with(user(user))) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); user = PasswordEncodedUser.withUserDetails(user) .authorities("profile:read", GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY) .build(); this.mockMvc.perform(get("/profile").with(user(user))) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=ott")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")); user = PasswordEncodedUser.withUserDetails(user) .authorities("profile:read", GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY) @@ -444,7 +444,7 @@ public class FormLoginConfigurerTests { this.mockMvc.perform(get("/login")).andExpect(status().isOk()); this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer"))) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); this.mockMvc .perform(post("/login").param("username", "rod") .param("password", "password") diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.java index 60337ace1f..e3091010a8 100644 --- a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.java +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.java @@ -69,7 +69,7 @@ public class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=ott")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")); // @formatter:on } @@ -80,7 +80,7 @@ public class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); // @formatter:on } @@ -91,7 +91,7 @@ public class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); // @formatter:on } diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.java index fe4f03e07b..872ad9e69d 100644 --- a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.java +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.java @@ -58,7 +58,7 @@ public class CustomAuthorizationManagerFactoryTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=ott")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")); // @formatter:on } diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java index 70dcb737a2..34f0a81fca 100644 --- a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/enableglobalmfa/EnableGlobalMultiFactorAuthenticationTests.java @@ -69,7 +69,7 @@ public class EnableGlobalMultiFactorAuthenticationTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=ott")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")); // @formatter:on } @@ -80,7 +80,7 @@ public class EnableGlobalMultiFactorAuthenticationTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); // @formatter:on } @@ -91,7 +91,7 @@ public class EnableGlobalMultiFactorAuthenticationTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); // @formatter:on } diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.java index 9aa6ee3da5..8ba5b7cf3e 100644 --- a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.java +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.java @@ -69,7 +69,7 @@ public class MultiFactorAuthenticationTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=ott")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")); // @formatter:on } @@ -80,7 +80,7 @@ public class MultiFactorAuthenticationTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); // @formatter:on } @@ -91,7 +91,7 @@ public class MultiFactorAuthenticationTests { // @formatter:off this.mockMvc.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=password")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")); // @formatter:on } diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/reauthentication/ReauthenticationTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/reauthentication/ReauthenticationTests.java index 81b810e84a..c9da833907 100644 --- a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/reauthentication/ReauthenticationTests.java +++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/reauthentication/ReauthenticationTests.java @@ -69,7 +69,7 @@ public class ReauthenticationTests { // @formatter:off this.mockMvc.perform(get("/profile")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=ott")); + .andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")); // @formatter:on } diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.kt index f0ac6b88d6..bff52b21a5 100644 --- a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.kt +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/authorizationmanagerfactory/AuthorizationManagerFactoryTests.kt @@ -68,7 +68,7 @@ class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=ott")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")) // @formatter:on } @@ -81,7 +81,7 @@ class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")) // @formatter:on } @@ -94,7 +94,7 @@ class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")) // @formatter:on } diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.kt index 839a83ab29..b182cbd007 100644 --- a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.kt +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/customauthorizationmanagerfactory/CustomAuthorizationManagerFactoryTests.kt @@ -55,7 +55,7 @@ class CustomAuthorizationManagerFactoryTests { // @formatter:off this.mockMvc!!.perform(get("/")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login?factor=ott")) + .andExpect(redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")) // @formatter:on } diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt index eea878f2da..c2303f1f3e 100644 --- a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/enableglobalmfa/AuthorizationManagerFactoryTests.kt @@ -68,7 +68,7 @@ class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=ott")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")) // @formatter:on } @@ -81,7 +81,7 @@ class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")) // @formatter:on } @@ -94,7 +94,7 @@ class AuthorizationManagerFactoryTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")) // @formatter:on } diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.kt index 945631448e..3de64b1064 100644 --- a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.kt +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/multifactorauthentication/MultiFactorAuthenticationTests.kt @@ -66,7 +66,7 @@ class MultiFactorAuthenticationTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=ott")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")) // @formatter:on } @@ -78,7 +78,7 @@ class MultiFactorAuthenticationTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")) // @formatter:on } @@ -90,7 +90,7 @@ class MultiFactorAuthenticationTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=password")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=password&factor.reason=missing")) // @formatter:on } diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/reauthentication/ReauthenticationTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/reauthentication/ReauthenticationTests.kt index 2ceb9d26cb..6cf9bd57c9 100644 --- a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/reauthentication/ReauthenticationTests.kt +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/reauthentication/ReauthenticationTests.kt @@ -68,7 +68,7 @@ class ReauthenticationTests { // @formatter:off this.mockMvc!!.perform(MockMvcRequestBuilders.get("/profile")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) - .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=ott")) + .andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor.type=ott&factor.reason=missing")) // @formatter:on } diff --git a/web/src/main/java/org/springframework/security/web/WebAttributes.java b/web/src/main/java/org/springframework/security/web/WebAttributes.java index 53ec1c698c..3ed65f0053 100644 --- a/web/src/main/java/org/springframework/security/web/WebAttributes.java +++ b/web/src/main/java/org/springframework/security/web/WebAttributes.java @@ -18,7 +18,6 @@ package org.springframework.security.web; import jakarta.servlet.http.HttpServletRequest; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; /** @@ -56,15 +55,16 @@ public final class WebAttributes { + ".WEB_INVOCATION_PRIVILEGE_EVALUATOR_ATTRIBUTE"; /** - * Used to set a {@code Collection} of {@link GrantedAuthority} instances into the - * {@link HttpServletRequest}. + * Used to set a {@code Collection} of + * {@link org.springframework.security.authorization.RequiredFactorError} instances + * into the {@link HttpServletRequest}. *
* Represents what authorities are missing to be authorized for the current request
*
* @since 7.0
* @see org.springframework.security.web.access.DelegatingMissingAuthorityAccessDeniedHandler
*/
- public static final String MISSING_AUTHORITIES = WebAttributes.class + ".MISSING_AUTHORITIES";
+ public static final String REQUIRED_FACTOR_ERRORS = WebAttributes.class + ".REQUIRED_FACTOR_ERRORS ";
private WebAttributes() {
}
diff --git a/web/src/main/java/org/springframework/security/web/access/DelegatingMissingAuthorityAccessDeniedHandler.java b/web/src/main/java/org/springframework/security/web/access/DelegatingMissingAuthorityAccessDeniedHandler.java
index 7260146b38..eee4e286aa 100644
--- a/web/src/main/java/org/springframework/security/web/access/DelegatingMissingAuthorityAccessDeniedHandler.java
+++ b/web/src/main/java/org/springframework/security/web/access/DelegatingMissingAuthorityAccessDeniedHandler.java
@@ -17,11 +17,11 @@
package org.springframework.security.web.access;
import java.io.IOException;
-import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -32,6 +32,10 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
+import org.springframework.security.authorization.AuthorizationResult;
+import org.springframework.security.authorization.FactorAuthorizationDecision;
+import org.springframework.security.authorization.RequiredFactor;
+import org.springframework.security.authorization.RequiredFactorError;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -93,15 +97,19 @@ public final class DelegatingMissingAuthorityAccessDeniedHandler implements Acce
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException denied)
throws IOException, ServletException {
- Collection