简体   繁体   English

添加额外的 Spring Security 方法注释

[英]Adding additional Spring Security method annotations

I'm writing a library that uses Spring Security and method security to check whether a user is licensed to perform a certain operation.我正在编写一个使用 Spring Security 和方法安全性的库来检查用户是否被许可执行某个操作。 This is in addition to the usual role-based security, and this is causing a problem.这是对通常的基于角色的安全性的补充,这会导致问题。

The annotations look like they do in this test class:注释看起来像他们在这个测试类中所做的:

@RestController
class TestController {

    @RolesAllowed("ROLE_USER")
    @Licensed("a")
    public ResponseEntity<String> a() {
        return ResponseEntity.ok("a");
    }

    @RolesAllowed("ROLE_USER")
    @Licensed("b")
    public ResponseEntity<String> b() {
        return ResponseEntity.ok("b");
    }

    @RolesAllowed("ROLE_USER")
    @Licensed("c")
    public ResponseEntity<String> c() {
        return ResponseEntity.ok("c");
    }
}

Having the annotations processed seems simple enough, because you add a customMethodSecurityDataSource :处理注释似乎很简单,因为您添加了一个customMethodSecurityDataSource

@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true
)
@Configuration
public class LicenceSecurityConfiguration extends GlobalMethodSecurityConfiguration {

    @Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
        return new LicensedAnnotationSecurityMetadataSource();
    }

    // more configurations
}

But the problem is in Spring's implementation:但问题出在 Spring 的实现中:

@Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
    DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
    synchronized (this.attributeCache) {
        Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
        // Check for canonical value indicating there is no config attribute,
        if (cached != null) {
            return cached;
        }
        // No cached value, so query the sources to find a result
        Collection<ConfigAttribute> attributes = null;
        for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
            attributes = s.getAttributes(method, targetClass);
            if (attributes != null && !attributes.isEmpty()) {
                break;
            }
        }
        // Put it in the cache.
        if (attributes == null || attributes.isEmpty()) {
            this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
            return NULL_CONFIG_ATTRIBUTE;
        }
        this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes));
        this.attributeCache.put(cacheKey, attributes);
        return attributes;
    }

My custom metadata source is processed first, and as soon as it finds an annotation that it recognises, it stops processing.我的自定义元数据源首先被处理,一旦找到它识别的注释,它就会停止处理。 Specifically, in this if-block:具体来说,在这个 if 块中:

if (attributes != null && !attributes.isEmpty()) {
    break;
}

The result is that my LicenceDecisionVoter votes to abstain;结果是我的LicenceDecisionVoter投弃权票; after all, there could be other annotation processors that check roles.毕竟,可能还有其他注释处理器来检查角色。 And because there are no more attributes to vote upon, only ACCESS_ABSTAIN is returned, and as per Spring's default and recommended configuration, access is denied.并且因为没有更多的属性可以投票,所以只返回ACCESS_ABSTAIN ,并且根据 Spring 的默认和推荐配置,访问被拒绝。 The roles are never checked.从不检查角色。

Do I have an alternative, other than to implement scanning for Spring's own annotation processors, like the @Secured and JSR-250 annotations?除了实现对 Spring 自己的注释处理器(如@Secured和 JSR-250 注释)的扫描之外,我还有其他选择吗?

Or was the mistake to use Spring Security in the first place for this specific purpose?还是首先出于此特定目的使用 Spring Security 是错误的?

As promised, the solution.正如所承诺的,解决方案。 It was more work than I imagined, and the code may have issues because it is partly copied from Spring, and some of that code looks dodgy (or at least, IntelliJ thinks it does).它的工作量比我想象的要多,而且代码可能有问题,因为它部分是从 Spring 复制的,而且其中一些代码看起来很狡猾(或者至少,IntelliJ 认为确实如此)。

The key is to remove the GlobalMethodSecurityConfiguration .关键是删除GlobalMethodSecurityConfiguration Leave that to the application itself.将其留给应用程序本身。 The (auto) configuration class looks like the following: (auto) 配置类如下所示:

@EnableConfigurationProperties(LicenceProperties.class)
@Configuration
@Import(LicensedMetadataSourceAdvisorRegistrar.class)
public class LicenceAutoConfiguration {

    @Bean public <T extends Licence> LicenceChecker<T> licenceChecker(
            @Lazy @Autowired final LicenceProperties properties,
            @Lazy @Autowired final LicenceFactory<T> factory
    ) throws InvalidSignatureException, LicenceExpiredException, WrappedApiException,
             IOException, ParseException, InvalidKeySpecException {
        final LicenceLoader loader = new LicenceLoader(factory.getPublicKey());
        final T licence = loader.load(properties.getLicenceFile(), factory.getType());
        return factory.getChecker(licence);
    }

    @Bean MethodSecurityInterceptor licenceSecurityInterceptor(
            final LicensedMetadataSource metadataSource,
            final LicenceChecker<?> licenceChecker
    ) {
        final MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
        interceptor.setAccessDecisionManager(decisionManager(licenceChecker));
        interceptor.setSecurityMetadataSource(metadataSource);
        return interceptor;
    }

    @Bean LicenceAccessDecisionManager decisionManager(@Autowired final LicenceChecker<?> licenceChecker) {
        return new LicenceAccessDecisionManager(licenceChecker);
    }

    @Bean LicensedMetadataSource licensedMetadataSource() {
        return new LicensedMetadataSource();
    }
}

The registrar:登记员:

public class LicensedMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(final AnnotationMetadata importingClassMetadata,
                                        final BeanDefinitionRegistry registry) {
        final BeanDefinitionBuilder advisor = BeanDefinitionBuilder
                .rootBeanDefinition(LicensedMetadataSourceAdvisor.class);
        advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        advisor.addConstructorArgReference("licensedMetadataSource");
        registry.registerBeanDefinition("licensedMetadataSourceAdvisor", advisor.getBeanDefinition());
    }
}

And finally, the advisor:最后,顾问:

public class LicensedMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {

    private final LicenceMetadataSourcePointcut pointcut = new LicenceMetadataSourcePointcut();

    private transient LicensedMetadataSource attributeSource;

    private transient BeanFactory beanFactory;
    private transient MethodInterceptor interceptor;

    private transient volatile Object adviceMonitor = new Object();

    public LicensedMetadataSourceAdvisor(final LicensedMetadataSource attributeSource) {
        this.attributeSource = attributeSource;
    }

    @Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override public Pointcut getPointcut() {
        return pointcut;
    }

    @Override public Advice getAdvice() {
        synchronized (this.adviceMonitor) {
            if (this.interceptor == null) {
                Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
                this.interceptor = this.beanFactory.getBean("licenceSecurityInterceptor", MethodInterceptor.class);
            }
            return this.interceptor;
        }
    }

    class LicenceMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

        @Override public boolean matches(final Method method, final Class<?> targetClass) {
            final LicensedMetadataSource source = LicensedMetadataSourceAdvisor.this.attributeSource;
            final Collection<ConfigAttribute> attributes = source.getAttributes(method, targetClass);
            return attributes != null && !attributes.isEmpty();
        }
    }
}

The latter two classes are copied and modified from Spring.后两个类是从 Spring 复制和修改的。 The advisor was copied from MethodSecurityMetadataSourceAdvisor , and that's a class that somebody at Spring might have a look at, because of the transient volatile synchronisation object (which I copied, because I can't yet establish if it should be final instead), and because it has a private method that is never used.顾问是从MethodSecurityMetadataSourceAdvisor复制的,这是一个 Spring 的某个人可能会查看的类,因为transient volatile同步对象(我复制了它,因为我还不能确定它是否应该是final ),并且因为它有一个从未使用过的私有方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM