My question is a duplicate of Custom annotation with spring security but it went unanswered and I believe there should be a simple solution to the problem.
Basically instead of doing:
@PreAuthorize("hasPermission(T(fully.qualified.Someclass).WHATEVER, T(fully.qualified.Permission).READ")
I would like to do:
@PreAuthorize(Someclass.WHATEVER, Permission.READ)
or possibly some custom annotation that will wire up easily with spring security
This seems much cleaner to me and I would like to be able to do it if I can.
Indeed you can implement a custom strongly typed security annotation, though this is rather bothersome. Declare your annotation
enum Permission { USER_LIST, USER_EDIT, USER_ADD, USER_ROLE_EDIT } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Permissions { Permission[] value(); }
Declare the custom implementation of org.springframework.security.access.ConfigAttribute
to be used by security pipeline
class SecurityAttribute implements ConfigAttribute { private final List<Permission> permissions; public SecurityAttribute(List<Permission> permissions) { this.permissions = permissions; } @Override public String getAttribute() { return permissions.stream().map(p -> p.name()).collect(Collectors.joining(",")); } }
Declare the custom implementation of org.springframework.security.access.method.MethodSecurityMetadataSource
to create the instances of SecurityAttribute
from annotations
class SecurityMetadataSource extends AbstractMethodSecurityMetadataSource { @Override public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) { //consult https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/access/prepost/PrePostAnnotationSecurityMetadataSource.java //to implement findAnnotation Permissions annotation = findAnnotation(method, targetClass, Permissions.class); if (annotation != null) { return Collections.singletonList(new SecurityAttribute(asList(annotation.value()))); } return Collections.emptyList(); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } }
At last declare the custom implementation org.springframework.security.access.AccessDecisionVoter
public class PermissionVoter implements AccessDecisionVoter<MethodInvocation> { @Override public boolean supports(ConfigAttribute attribute) { return attribute instanceof SecurityAttribute; } @Override public boolean supports(Class<?> clazz) { return MethodInvocation.class.isAssignableFrom(clazz); } @Override public int vote(Authentication authentication, MethodInvocation object, Collection<ConfigAttribute> attributes) { Optional<SecurityAttribute> securityAttribute = attributes.stream() .filter(attr -> attr instanceof SecurityAttribute).map(SecurityAttribute.class::cast).findFirst(); if(!securityAttribute.isPresent()){ return AccessDecisionVoter.ACCESS_ABSTAIN; } //authorize your principal from authentication object //against permissions and return ACCESS_GRANTED or ACCESS_DENIED } }
and now bring them all together in your MethodSecurityConfig
@Configuration @EnableGlobalMethodSecurity class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() { return new ScpSecurityMetadataSource(); } @Override protected AccessDecisionManager accessDecisionManager() { return new AffirmativeBased(Collections.singletonList(new PermissionVoter())); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With