Use RequiredFactorErrors
Closes gh-18002
This commit is contained in:
+4
-4
@@ -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")
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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}.
|
||||
* <p>
|
||||
* 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() {
|
||||
}
|
||||
|
||||
+71
-10
@@ -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<GrantedAuthority> authorities = missingAuthorities(denied);
|
||||
for (GrantedAuthority needed : authorities) {
|
||||
AuthenticationEntryPoint entryPoint = this.entryPoints.get(needed.getAuthority());
|
||||
List<AuthorityRequiredFactorErrorEntry> authorityErrors = authorityErrors(denied);
|
||||
for (AuthorityRequiredFactorErrorEntry authorityError : authorityErrors) {
|
||||
String requiredAuthority = authorityError.getAuthority();
|
||||
AuthenticationEntryPoint entryPoint = this.entryPoints.get(requiredAuthority);
|
||||
if (entryPoint == null) {
|
||||
continue;
|
||||
}
|
||||
this.requestCache.saveRequest(request, response);
|
||||
request.setAttribute(WebAttributes.MISSING_AUTHORITIES, List.of(needed));
|
||||
String message = String.format("Missing Authorities %s", List.of(needed));
|
||||
RequiredFactorError required = authorityError.getError();
|
||||
if (required != null) {
|
||||
request.setAttribute(WebAttributes.REQUIRED_FACTOR_ERRORS, List.of(required));
|
||||
}
|
||||
String message = String.format("Missing Authorities %s", requiredAuthority);
|
||||
AuthenticationException ex = new InsufficientAuthenticationException(message, denied);
|
||||
entryPoint.commence(request, response, ex);
|
||||
return;
|
||||
@@ -131,15 +139,39 @@ public final class DelegatingMissingAuthorityAccessDeniedHandler implements Acce
|
||||
this.requestCache = requestCache;
|
||||
}
|
||||
|
||||
private Collection<GrantedAuthority> missingAuthorities(AccessDeniedException ex) {
|
||||
private List<AuthorityRequiredFactorErrorEntry> authorityErrors(AccessDeniedException ex) {
|
||||
AuthorizationDeniedException denied = findAuthorizationDeniedException(ex);
|
||||
if (denied == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (!(denied.getAuthorizationResult() instanceof AuthorityAuthorizationDecision authorization)) {
|
||||
return List.of();
|
||||
AuthorizationResult authorizationResult = denied.getAuthorizationResult();
|
||||
if (authorizationResult instanceof FactorAuthorizationDecision factorDecision) {
|
||||
// @formatter:off
|
||||
return factorDecision.getFactorErrors().stream()
|
||||
.map((error) -> {
|
||||
String authority = error.getRequiredFactor().getAuthority();
|
||||
return new AuthorityRequiredFactorErrorEntry(authority, error);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
// @formatter:on
|
||||
}
|
||||
return authorization.getAuthorities();
|
||||
if (authorizationResult instanceof AuthorityAuthorizationDecision authorityDecision) {
|
||||
// @formatter:off
|
||||
return authorityDecision.getAuthorities().stream()
|
||||
.map((grantedAuthority) -> {
|
||||
String authority = grantedAuthority.getAuthority();
|
||||
if (authority.startsWith("FACTOR_")) {
|
||||
RequiredFactor required = RequiredFactor.withAuthority(authority).build();
|
||||
return new AuthorityRequiredFactorErrorEntry(authority, RequiredFactorError.createMissing(required));
|
||||
}
|
||||
else {
|
||||
return new AuthorityRequiredFactorErrorEntry(authority, null);
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
// @formatter:on
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private @Nullable AuthorizationDeniedException findAuthorizationDeniedException(AccessDeniedException ex) {
|
||||
@@ -206,4 +238,33 @@ public final class DelegatingMissingAuthorityAccessDeniedHandler implements Acce
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping of a {@link GrantedAuthority#getAuthority()} to a possibly null
|
||||
* {@link RequiredFactorError}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
private static final class AuthorityRequiredFactorErrorEntry {
|
||||
|
||||
private final String authority;
|
||||
|
||||
private final @Nullable RequiredFactorError error;
|
||||
|
||||
private AuthorityRequiredFactorErrorEntry(String authority, @Nullable RequiredFactorError error) {
|
||||
Assert.notNull(authority, "authority cannot be null");
|
||||
this.authority = authority;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
private String getAuthority() {
|
||||
return this.authority;
|
||||
}
|
||||
|
||||
private @Nullable RequiredFactorError getError() {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-7
@@ -18,6 +18,7 @@ package org.springframework.security.web.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
@@ -31,8 +32,8 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.authorization.RequiredFactorError;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.PortMapper;
|
||||
@@ -118,16 +119,22 @@ public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoin
|
||||
@SuppressWarnings("unchecked")
|
||||
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException exception) {
|
||||
Collection<GrantedAuthority> authorities = getAttribute(request, WebAttributes.MISSING_AUTHORITIES,
|
||||
Collection<RequiredFactorError> factorErrors = getAttribute(request, WebAttributes.REQUIRED_FACTOR_ERRORS,
|
||||
Collection.class);
|
||||
if (CollectionUtils.isEmpty(authorities)) {
|
||||
if (CollectionUtils.isEmpty(factorErrors)) {
|
||||
return getLoginFormUrl();
|
||||
}
|
||||
Collection<String> factors = authorities.stream()
|
||||
.filter((a) -> a.getAuthority().startsWith(FACTOR_PREFIX))
|
||||
.map((a) -> a.getAuthority().substring(FACTOR_PREFIX.length()).toLowerCase(Locale.ROOT))
|
||||
List<String> factorTypes = factorErrors.stream()
|
||||
.map((factorError) -> factorError.getRequiredFactor().getAuthority())
|
||||
.map((a) -> a.substring(FACTOR_PREFIX.length()).toLowerCase(Locale.ROOT))
|
||||
.toList();
|
||||
return UriComponentsBuilder.fromUriString(getLoginFormUrl()).queryParam("factor", factors).toUriString();
|
||||
List<String> factorReasons = factorErrors.stream()
|
||||
.map((factorError) -> factorError.isExpired() ? "expired" : "missing")
|
||||
.toList();
|
||||
return UriComponentsBuilder.fromUriString(getLoginFormUrl())
|
||||
.queryParam("factor.type", factorTypes)
|
||||
.queryParam("factor.reason", factorReasons)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
private static <T> @Nullable T getAttribute(HttpServletRequest request, String name, Class<T> clazz) {
|
||||
|
||||
+6
-4
@@ -18,10 +18,10 @@ package org.springframework.security.web.authentication.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -88,9 +88,11 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
|
||||
private @Nullable String rememberMeParameter;
|
||||
|
||||
private final String factorParameter = "factor";
|
||||
private final String factorTypeParameter = "factor.type";
|
||||
|
||||
private final Collection<String> allowedParameters = List.of(this.factorParameter);
|
||||
private final String factorReasonParameter = "factor.reason";
|
||||
|
||||
private final Set<String> allowedParameters = Set.of(this.factorTypeParameter, this.factorReasonParameter);
|
||||
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
private Map<String, String> oauth2AuthenticationUrlToClientName;
|
||||
@@ -281,7 +283,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
}
|
||||
|
||||
private Predicate<String> wantsAuthority(HttpServletRequest request) {
|
||||
String[] authorities = request.getParameterValues(this.factorParameter);
|
||||
String[] authorities = request.getParameterValues(this.factorTypeParameter);
|
||||
if (authorities == null) {
|
||||
return (authority) -> true;
|
||||
}
|
||||
|
||||
+5
-3
@@ -204,7 +204,8 @@ public class DefaultLoginPageGeneratingFilterTests {
|
||||
filter.setOneTimeTokenEnabled(true);
|
||||
filter.setOneTimeTokenGenerationUrl("/ott/authenticate");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(TestMockHttpServletRequests.get("/login?factor=ott").build(), response, this.chain);
|
||||
filter.doFilter(TestMockHttpServletRequests.get("/login?factor.type=ott&factor.reason=missing").build(),
|
||||
response, this.chain);
|
||||
assertThat(response.getContentAsString()).contains("Request a One-Time Token");
|
||||
assertThat(response.getContentAsString()).contains("""
|
||||
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
|
||||
@@ -231,8 +232,9 @@ public class DefaultLoginPageGeneratingFilterTests {
|
||||
filter.setOneTimeTokenEnabled(true);
|
||||
filter.setOneTimeTokenGenerationUrl("/ott/authenticate");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(TestMockHttpServletRequests.get("/login?factor=ott&factor=password").build(), response,
|
||||
this.chain);
|
||||
filter.doFilter(TestMockHttpServletRequests
|
||||
.get("/login?factor.type=ott&factor.type=password&factor.reason=missing&factor.reason=missing")
|
||||
.build(), response, this.chain);
|
||||
assertThat(response.getContentAsString()).contains("Request a One-Time Token");
|
||||
assertThat(response.getContentAsString()).contains("""
|
||||
<form id="ott-form" class="login-form" method="post" action="/ott/authenticate">
|
||||
|
||||
Reference in New Issue
Block a user