Skip to content

SecuredAuthorizationManager should cache annotation's value #12335

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -16,8 +16,6 @@

package org.springframework.security.authorization;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

Expand All @@ -43,7 +41,7 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
private final Set<String> authorities;

private AuthorityAuthorizationManager(String... authorities) {
this.authorities = new HashSet<>(Arrays.asList(authorities));
this.authorities = Set.of(authorities);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -17,14 +17,18 @@
package org.springframework.security.authorization.method;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import org.aopalliance.intercept.MethodInvocation;

import org.springframework.aop.support.AopUtils;
import org.springframework.lang.NonNull;
import org.springframework.core.MethodClassKey;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
Expand All @@ -39,7 +43,9 @@
*/
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {

private final SecuredAuthorizationManagerRegistry registry = new SecuredAuthorizationManagerRegistry();
private final AuthoritiesAuthorizationManager delegate = new AuthoritiesAuthorizationManager();

private final Map<MethodClassKey, Set<String>> cachedAuthorities = new ConcurrentHashMap<>();

/**
* Determine if an {@link Authentication} has access to a method by evaluating the
Expand All @@ -51,26 +57,28 @@ public final class SecuredAuthorizationManager implements AuthorizationManager<M
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(mi);
return delegate.check(authentication, mi);
Set<String> authorities = getAuthorities(mi);
return authorities.isEmpty() ? null : this.delegate.check(authentication, authorities);
}

private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {

@NonNull
@Override
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Secured secured = findSecuredAnnotation(specificMethod);
return (secured != null) ? AuthorityAuthorizationManager.hasAnyAuthority(secured.value()) : NULL_MANAGER;
}
private Set<String> getAuthorities(MethodInvocation methodInvocation) {
Method method = methodInvocation.getMethod();
Object target = methodInvocation.getThis();
Class<?> targetClass = (target != null) ? target.getClass() : null;
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
return this.cachedAuthorities.computeIfAbsent(cacheKey, (k) -> resolveAuthorities(method, targetClass));
}

private Secured findSecuredAnnotation(Method method) {
Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
return (secured != null) ? secured
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class);
}
private Set<String> resolveAuthorities(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Secured secured = findSecuredAnnotation(specificMethod);
return (secured != null) ? Set.of(secured.value()) : Collections.emptySet();
}

private Secured findSecuredAnnotation(Method method) {
Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
return (secured != null) ? secured
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand Down Expand Up @@ -74,14 +74,14 @@ void checkWhenUserHasNotAnyAuthorityThenDeniedDecision() {
}

@Test
void hasRoleWhenRoleHierarchySetThenGreaterRoleTakesPrecedence() {
void checkWhenRoleHierarchySetThenGreaterRoleTakesPrecedence() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will you please put this in a separate commit with a comment that describes why the change was made? By doing that, each commit will stay focused on one atomic change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

AuthoritiesAuthorizationManager manager = new AuthoritiesAuthorizationManager();
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
manager.setRoleHierarchy(roleHierarchy);
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
"ROLE_ADMIN");
assertThat(manager.check(authentication, Collections.singleton("ROLE_ADMIN")).isGranted()).isTrue();
assertThat(manager.check(authentication, Collections.singleton("ROLE_USER")).isGranted()).isTrue();
}

}