Spring Security @PreAuthorization pass enums in directly

前端 未结 4 1496
清歌不尽
清歌不尽 2021-02-07 03:29

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 ins

相关标签:
4条回答
  • 2021-02-07 03:48

    You can create static annotations like this:

    @ReadPermission
    

    By moving @PreAuthorize annotation to @ReadPermissiondefinition:

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

    Benefit of this is, that you can then change Spring SPEL expression in one place, instead of modifying it on every method. One more plus is, that you can use this annotation on Class level - every method then would be secured with this annotation. It's useful for AdminControllers etc..

    0 讨论(0)
  • 2021-02-07 03:59

    Facing the same issue, I ended up with a hybrid solution. I am using Spring-El and a custom bean to provide my own hasPermission() method which accepts an Enum. Given that Spring does an automatic string->enum conversion, at runtime, I will get a runtime exception that a particular enum does not exist if there is a typo in the string. Not the ideal solution (would have rather had something that failed at compile-time), but an acceptable compromise. It gives me some semi-type safety.

    @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 a normal enum definition:

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

    Hope this can help someone else out in the future.

    0 讨论(0)
  • 2021-02-07 04:03

    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()));
        }
    }
    
    0 讨论(0)
  • 2021-02-07 04:07

    I did that way :

    1 - Define your enum referencing a public final static String "VALUE" like this

    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+"')")
    
    0 讨论(0)
提交回复
热议问题