Spring Security @PreAuthorization直接传递枚举

时间:2013-10-10 18:35:53

标签: java spring spring-security authorization spring-annotations

我的问题是Custom annotation with spring security的副本,但没有得到答复,我相信应该有一个简单的解决方案。

基本上不是做:

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

我想这样做:

@PreAuthorize(Someclass.WHATEVER, Permission.READ)

或者可能是一些自定义注释,可以很容易地与弹簧安全性连接

这对我来说似乎更清洁,如果可以,我希望能够做到这一点。

4 个答案:

答案 0 :(得分:7)

面对同样的问题,我最终得到了一个混合解决方案。我使用Spring-El和自定义bean来提供我自己的hasPermission()方法,该方法接受Enum。鉴于Spring进行了自动string->enum转换,在运行时,如果字符串中存在拼写错误,我将得到一个运行时异常,即特定的枚举不存在。不是理想的解决方案(在编译时会有一些失败的东西),但是可接受的妥协。它给了我一些半安全性。

@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;
    }
}

使用如下:

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

Permission只是一个正常的枚举定义:

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

希望将来可以帮助其他人。

答案 1 :(得分:3)

实际上,您可以实现自定义的强类型安全注释,尽管这很麻烦。 声明您的注释

enum Permission {
    USER_LIST,
    USER_EDIT,
    USER_ADD,
    USER_ROLE_EDIT
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Permissions {
    Permission[] value();
}

声明安全管道使用的org.springframework.security.access.ConfigAttribute的自定义实现

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(","));
    }
}

声明org.springframework.security.access.method.MethodSecurityMetadataSource的自定义实现,以根据注释创建SecurityAttribute的实例

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;
    } 

}

最后声明自定义实现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

    }

}

,现在将它们全部放在您的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()));
    }
}

答案 2 :(得分:2)

您可以像这样创建静态注释:

@ReadPermission

通过将@PreAuthorize注释移至@ReadPermission定义:

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

}

这样做的好处是,您可以在一个地方更改Spring SPEL表达式,而不必在每种方法上都进行修改。另外一个好处是,您可以在类级别使用此注释-然后,将使用此注释保护每个方法。这对AdminControllers等很有用。

答案 3 :(得分:1)

我这样做了:

1 - 定义引用公共最终静态字符串“VALUE”的枚举,如下所示

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 - @PreAuthorize中的Concat MyEnum值

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