We are trying to migrate away from spring-security's deprecated AccessDecisionVoter
Our Config includes a decisionManager that takes a few AccessDecisionVoter and then decides based on all the input.
class CollectingUnanimousBasedAccessDecisionManager extends AbstractAccessDecisionManager {
CollectingUnanimousBasedAccessDecisionManager(List<AccessRightDecisionVoter> decisionVoters) {
super(decisionVoters)
}
void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
int deniedDecisions = 0
List violatedAccessRights = []
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes)
switch (result) {
case AccessDecisionVoter.ACCESS_DENIED:
deniedDecisions++
violatedAccessRights.addAll(((AccessRightDecisionVoter) voter).getSupportedAccessRights())
break
}
}
if (deniedDecisions > 0) {
throw new AccessDeniedExceptionWithCause("Access is denied", violatedAccessRights)
}
}
}
Our Config looks like this:
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
class AuthorizationConfig extends GlobalMethodSecurityConfiguration {
@Autowired
ServiceCallVoter serviceCallVoter
@Autowired
AuthorityAndScopeVoter authorityAndScopeVoter
@Autowired
CallOtherVoterVoter callOtherVoterVoter
@Autowired
AuthorityVoter authorityVoter
@Autowired
Environment environment
@Override
AccessDecisionManager accessDecisionManager() {
new CollectingUnanimousBasedAccessDecisionManager([
serviceCallVoter,
authorityAndScopeVoter,
callOtherVoterVoter,
authorityVoter
])
}
}
We then have for example a ServiceCallVoter, which has other dependencies and calls eg. a database for input, or another service:
@Component
class ServiceCallVoter extends AbstractIdVoter {
@Override
String[] getSupportedAccessRights() {
[SERVICE_CALL]
}
@Override
int voteInternal(String id, Authentication authentication, ReflectiveMethodInvocation methodToAuthorize, Collection<ConfigAttribute> configAttributes) {
if (true) { // some service call
return ACCESS_GRANTED
}
return ACCESS_DENIED
}
}
And also for example an Authority Voter, which is looking into the Authentication authorities or scopes:
@Component
abstract class AbstractAuthorityVoter extends AccessRightDecisionVoter {
abstract List<String> getAuthorities()
@Override
int vote(Authentication authentication, ReflectiveMethodInvocation objectToAuthorize, Collection<ConfigAttribute> configAttributes) {
if (!supportsAnyConfigAttribute(configAttributes)) {
return ACCESS_ABSTAIN
}
def hasAuthority = authentication.authorities.any {
String authority = it.authority
getAuthorities().any { it == authority }
}
return hasAuthority ? ACCESS_GRANTED : ACCESS_DENIED
}
}
For the simpler services only using the AbstractAuthorityVoter
we found the solution to use a @PreAuthorize
annotation:
@PreAuthorize("hasAnyAuthority('AUTHORITY_1', 'SCOPE_SCHREIBEN')")
But for the more complex services, which use a combination of multiple voters, (@Secured([SCOPE_SCHREIBEN, CALL_OTHER_VOTER])
), we could not yet get it to work.
How would you migrate those classes to the AuthorizationManager
?
All the classes can be found https://github.com/huehnerlady/demo-spring-security-6