|
|
|
@ -17,11 +17,11 @@ |
|
|
|
package org.springframework.security.web.access; |
|
|
|
package org.springframework.security.web.access; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.IOException; |
|
|
|
import java.io.IOException; |
|
|
|
import java.util.Collection; |
|
|
|
|
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.function.Consumer; |
|
|
|
import java.util.function.Consumer; |
|
|
|
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
|
|
|
import jakarta.servlet.ServletException; |
|
|
|
import jakarta.servlet.ServletException; |
|
|
|
import jakarta.servlet.http.HttpServletRequest; |
|
|
|
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.authentication.InsufficientAuthenticationException; |
|
|
|
import org.springframework.security.authorization.AuthorityAuthorizationDecision; |
|
|
|
import org.springframework.security.authorization.AuthorityAuthorizationDecision; |
|
|
|
import org.springframework.security.authorization.AuthorizationDeniedException; |
|
|
|
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.AuthenticationException; |
|
|
|
import org.springframework.security.core.GrantedAuthority; |
|
|
|
import org.springframework.security.core.GrantedAuthority; |
|
|
|
import org.springframework.security.web.AuthenticationEntryPoint; |
|
|
|
import org.springframework.security.web.AuthenticationEntryPoint; |
|
|
|
@ -93,15 +97,19 @@ public final class DelegatingMissingAuthorityAccessDeniedHandler implements Acce |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException denied) |
|
|
|
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException denied) |
|
|
|
throws IOException, ServletException { |
|
|
|
throws IOException, ServletException { |
|
|
|
Collection<GrantedAuthority> authorities = missingAuthorities(denied); |
|
|
|
List<AuthorityRequiredFactorErrorEntry> authorityErrors = authorityErrors(denied); |
|
|
|
for (GrantedAuthority needed : authorities) { |
|
|
|
for (AuthorityRequiredFactorErrorEntry authorityError : authorityErrors) { |
|
|
|
AuthenticationEntryPoint entryPoint = this.entryPoints.get(needed.getAuthority()); |
|
|
|
String requiredAuthority = authorityError.getAuthority(); |
|
|
|
|
|
|
|
AuthenticationEntryPoint entryPoint = this.entryPoints.get(requiredAuthority); |
|
|
|
if (entryPoint == null) { |
|
|
|
if (entryPoint == null) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
this.requestCache.saveRequest(request, response); |
|
|
|
this.requestCache.saveRequest(request, response); |
|
|
|
request.setAttribute(WebAttributes.MISSING_AUTHORITIES, List.of(needed)); |
|
|
|
RequiredFactorError required = authorityError.getError(); |
|
|
|
String message = String.format("Missing Authorities %s", List.of(needed)); |
|
|
|
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); |
|
|
|
AuthenticationException ex = new InsufficientAuthenticationException(message, denied); |
|
|
|
entryPoint.commence(request, response, ex); |
|
|
|
entryPoint.commence(request, response, ex); |
|
|
|
return; |
|
|
|
return; |
|
|
|
@ -131,15 +139,39 @@ public final class DelegatingMissingAuthorityAccessDeniedHandler implements Acce |
|
|
|
this.requestCache = requestCache; |
|
|
|
this.requestCache = requestCache; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Collection<GrantedAuthority> missingAuthorities(AccessDeniedException ex) { |
|
|
|
private List<AuthorityRequiredFactorErrorEntry> authorityErrors(AccessDeniedException ex) { |
|
|
|
AuthorizationDeniedException denied = findAuthorizationDeniedException(ex); |
|
|
|
AuthorizationDeniedException denied = findAuthorizationDeniedException(ex); |
|
|
|
if (denied == null) { |
|
|
|
if (denied == null) { |
|
|
|
return List.of(); |
|
|
|
return List.of(); |
|
|
|
} |
|
|
|
} |
|
|
|
if (!(denied.getAuthorizationResult() instanceof AuthorityAuthorizationDecision authorization)) { |
|
|
|
AuthorizationResult authorizationResult = denied.getAuthorizationResult(); |
|
|
|
return List.of(); |
|
|
|
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
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
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 authorization.getAuthorities(); |
|
|
|
return List.of(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private @Nullable AuthorizationDeniedException findAuthorizationDeniedException(AccessDeniedException ex) { |
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|