Spring Security @PreAuthorization skips enumerations directly

My question is a duplicate of User annotation with spring security , but it went unanswered, and I believe there should be a simple solution to the problem.

Basically instead:

@PreAuthorize("hasPermission(T(fully.qualified.Someclass).WHATEVER, T(fully.qualified.Permission).READ") 

I'd like to do:

 @PreAuthorize(Someclass.WHATEVER, Permission.READ) 

or maybe some custom annotations that easily connect with spring security

It seems a lot cleaner to me, and I would like to do it if I can.

+11
java spring spring-annotations spring-security authorization
source share
4 answers

Based on the same problem, I ended up with a hybrid solution. I use Spring-El and a custom bean to provide my own hasPermission() method, which accepts Enum. Given that Spring performs the automatic conversion of string->enum , at runtime I get an exception at runtime if the specific enumeration does not exist, if there is a typo in the string. Not an ideal solution (most likely, there was something that failed during compilation), but an acceptable compromise. This gives me some semi-type security.

 @Component("securityService") public class SecurityService { public boolean hasPermission( Permission...permissions){ // loop over each submitted role and validate the user has at least one Collection<? extends GrantedAuthority> userAuthorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); for( Permission permission : permissions){ if( userAuthorities.contains( new SimpleGrantedAuthority(permission.name()))) return true; } // no matching role found return false; } } 

Used as follows:

 @PreAuthorize("@securityService.hasPermission({'USER_ADD'})") public User addUser(User user){ // create the user return userRepository.save( user ); } 

Where Permission is just the normal definition of an enum:

 public enum Permission { USER_LIST, USER_EDIT, USER_ADD, USER_ROLE_EDIT } 

Hope this helps someone else in the future.

+7
source share

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) { //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; } } 

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; } //authorize your principal from authentication object //against permissions and return ACCESS_GRANTED or ACCESS_DENIED } } 

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())); } } 
+5
source share

I did like this:

1 - Define your enumeration referencing the public final static string "VALUE" as it is

 public enum MyEnum { ENUM_A(Names.ENUM_A); private String value; private MyEnum (String value) { this.value = value; } public static class Names { public final static String ENUM_A = "ENUM_A"; } } 

2 - Concat MyEnum values ​​in @PreAuthorize

 @PreAuthorize("hasPermission('myDomain', '"+ MyEnum.Names.ENUM_A+"')") 
+3
source share

You can create static annotations as follows:

 @ReadPermission 

@PreAuthorize moving the @PreAuthorize annotation to the @PreAuthorize definition:

  @Inherited    
 @PreAuthorize ("hasRole (T (fully.qualified.Permission) .READ.roleName ())")
 public @interface ReadPermission {

 }

The advantage of this is that you can change the SPEL Spring expression in one place, rather than changing it in every method. Another plus is that you can use this annotation at the class level - each method will be protected by this annotation. This is useful for admincontrollers, etc ..

+2
source share