弹簧安全@PreAuthorization直接通过枚举

我的问题是具有弹簧安全性的自定义注释的副本,但它没有得到解答,我相信应该有一个简单的解决方案来解决这个问题。

基本上不是做:

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

我想做:

@PreAuthorize(Someclass.WHATEVER, Permission.READ)

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

这对我来说似乎更干净,如果可以的话,我希望能够做到这一点。


答案 1

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

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

声明 的自定义实现,以创建 from 批注的实例org.springframework.security.access.method.MethodSecurityMetadataSourceSecurityAttribute

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

您可以创建如下静态批注:

@ReadPermission

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

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

这样做的好处是,您可以在一个地方更改Spring SPEL表达式,而不是在每个方法上修改它。

还有一个好处是,您可以在类级别使用此注释 - 然后每个方法都将使用此注释进行保护。它对管理员控制器等很有用。


推荐