Verify security rules with AuthorizationManager

See gh-1806
This commit is contained in:
rstoyanchev
2024-10-29 10:00:57 +00:00
parent 112d58bcde
commit 3f0234c4f6
3 changed files with 79 additions and 26 deletions

View File

@@ -18,17 +18,15 @@ package org.springframework.webflow.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.StateDefinition;
import org.springframework.webflow.definition.TransitionDefinition;
@@ -41,10 +39,26 @@ import org.springframework.webflow.execution.RequestContext;
*
* @author Scott Andrews
*/
@SuppressWarnings("deprecation")
public class SecurityFlowExecutionListener implements FlowExecutionListener {
private Function<SecurityRule, AuthorizationManager<Object>> authorizationManagerInitializer =
SecurityRule::getAuthorizationManager;
private AccessDecisionManager accessDecisionManager;
/**
* Provide a function that determines the {@link AuthorizationManager} to use
* for a given {@link SecurityRule}.
* <p>By default, {@link SecurityRule#getAuthorizationManager()} is used.
* @param initializer the function to use
* @since 3.0.1
*/
public void setAuthorizationManagerInitializer(Function<SecurityRule, AuthorizationManager<Object>> initializer) {
Assert.notNull(initializer, "'initializer' is required");
this.authorizationManagerInitializer = initializer;
}
/**
* Get the access decision manager that makes flow authorization decisions.
* @return the decision manager
@@ -89,33 +103,45 @@ public class SecurityFlowExecutionListener implements FlowExecutionListener {
* @param rule the rule to base the decision
* @param object the execution listener phase
*/
@SuppressWarnings("deprecation")
protected void decide(SecurityRule rule, Object object) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<ConfigAttribute> configAttributes = getConfigAttributes(rule);
if (accessDecisionManager != null) {
accessDecisionManager.decide(authentication, object, configAttributes);
AccessDecisionManager decisionManager =
(this.accessDecisionManager != null ? this.accessDecisionManager : createAccessDecisionManager(rule));
if (decisionManager != null) {
accessDecisionManager.decide(authentication, object, getConfigAttributes(rule));
} else {
createManager(rule).decide(authentication, object, configAttributes);
AuthorizationManager<Object> manager = this.authorizationManagerInitializer.apply(rule);
manager.verify(() -> authentication, object);
}
}
private AbstractAccessDecisionManager createManager(SecurityRule rule) {
List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<>();
voters.add(new RoleVoter());
if (rule.getComparisonType() == SecurityRule.COMPARISON_ANY) {
return new AffirmativeBased(voters);
} else if (rule.getComparisonType() == SecurityRule.COMPARISON_ALL) {
return new UnanimousBased(voters);
} else {
throw new IllegalStateException("Unknown SecurityRule match type: " + rule.getComparisonType());
}
/**
* Return an {@link AccessDecisionManager} for the SecurityRule.
* <p>By default, returns {@code null} in which case an
* {@link AuthorizationManager} is used instead of {@code AccessDecisionManager}.
* @param rule the rule to check
* @return the manager to use, or {@code null}
* @deprecated in favor of using an {@code AuthorizationManager} by setting
* {@link #setAuthorizationManagerInitializer(Function)} instead
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated(since = "3.0.1", forRemoval = true)
protected AccessDecisionManager createAccessDecisionManager(SecurityRule rule) {
return null;
}
/**
* Convert SecurityRule into a form understood by Spring Security
* @param rule the rule to convert
* @return list of ConfigAttributes for Spring Security
* @deprecated in favor of using an {@code AuthorizationManager} by setting
* {@link #setAuthorizationManagerInitializer(Function)} instead
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated(since = "3.0.1", forRemoval = true)
protected Collection<ConfigAttribute> getConfigAttributes(SecurityRule rule) {
List<ConfigAttribute> configAttributes = new ArrayList<>();
for (String attribute : rule.getAttributes()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2004-2012 the original author or authors.
* Copyright 2004-2024 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.
@@ -18,6 +18,9 @@ package org.springframework.webflow.security;
import java.util.Collection;
import java.util.HashSet;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.util.StringUtils;
/**
@@ -46,6 +49,8 @@ public class SecurityRule {
private short comparisonType = COMPARISON_ANY;
private AuthorizationManager<Object> authorizationManager;
/**
* Convert attributes to comma separated String
* @param attributes the attributes to convert
@@ -102,4 +107,24 @@ public class SecurityRule {
public void setComparisonType(short comparisonType) {
this.comparisonType = comparisonType;
}
/**
* Return an {@link AuthorizationManager} for this security config based on
* {@link AuthorityAuthorizationManager}.
*/
@SuppressWarnings("unchecked")
public AuthorizationManager<Object> getAuthorizationManager() {
if (this.authorizationManager == null) {
this.authorizationManager = switch (this.comparisonType) {
case SecurityRule.COMPARISON_ANY ->
AuthorityAuthorizationManager.hasAnyAuthority(this.attributes.toArray(new String[0]));
case SecurityRule.COMPARISON_ALL -> AuthorizationManagers.allOf(this.attributes.stream()
.map(AuthorityAuthorizationManager::hasAuthority)
.toArray(AuthorizationManager[]::new));
default -> throw new IllegalStateException("Unknown SecurityRule match type: " + this.comparisonType);
};
}
return this.authorizationManager;
}
}

View File

@@ -1,13 +1,11 @@
package org.springframework.webflow.security;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -25,6 +23,8 @@ import org.springframework.webflow.engine.support.DefaultTargetStateResolver;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.test.MockRequestContext;
import static org.junit.jupiter.api.Assertions.fail;
public class SecurityFlowExecutionListenerTests {
@Test
@@ -167,8 +167,10 @@ public class SecurityFlowExecutionListenerTests {
}
private Authentication getAuthentication() {
List<GrantedAuthority> authorities = Arrays.<GrantedAuthority> asList(new SimpleGrantedAuthority("ROLE_1"),
new SimpleGrantedAuthority("ROLE_2"), new SimpleGrantedAuthority("ROLE_3"));
return new UsernamePasswordAuthenticationToken("test", "", authorities);
return new UsernamePasswordAuthenticationToken("test", "",
List.<GrantedAuthority>of(
new SimpleGrantedAuthority("ROLE_1"),
new SimpleGrantedAuthority("ROLE_2"),
new SimpleGrantedAuthority("ROLE_3")));
}
}