In fact, you can implement custom strongly typed security annotations, although this is rather tedious. Announce annotation
enum Permission { USER_LIST, USER_EDIT, USER_ADD, USER_ROLE_EDIT } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Permissions { Permission[] value(); }
Declare that the custom implementation of org.springframework.security.access.ConfigAttribute will be used by the 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 a custom implementation of org.springframework.security.access.method.MethodSecurityMetadataSource to instantiate SecurityAttribute from annotations
class SecurityMetadataSource extends AbstractMethodSecurityMetadataSource { @Override public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
Finally, declare a custom implementation of 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; }
now get them all together in 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())); } }
Dima korn
source share