添加额外的spring安全方法注解

rfbsl7qr  于 2021-09-30  发布在  Java
关注(0)|答案(1)|浏览(385)

我正在编写一个库,它使用Spring Security 和方法安全性来检查用户是否获得执行特定操作的许可。这是对通常基于角色的安全性的补充,这导致了一个问题。
注解与此测试类中的注解类似:

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

处理注解似乎很简单,因为您添加了 customMethodSecurityDataSource :

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

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

    // more configurations
}

但问题在于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;
    }

首先处理我的自定义元数据源,一旦它找到它识别的注解,它就会停止处理。具体而言,在此if块中:

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

结果是我的 LicenceDecisionVoter 弃权票;毕竟,可能还有其他注解处理器来检查角色。因为没有更多的属性可以投票,只有 ACCESS_ABSTAIN 返回,并且根据spring的默认和推荐配置,访问被拒绝。角色从不被检查。
除了为spring自己的注解处理器(如 @Secured 和jsr-250注解?
或者说,将spring security用于此特定目的是错误的?

jtoj6r0c

jtoj6r0c1#

正如承诺的那样,解决方案是可行的。这比我想象的要多,而且代码可能有问题,因为它部分是从spring复制的,而且其中一些代码看起来很不可靠(或者至少intellij认为是这样)。
关键是要移除 GlobalMethodSecurityConfiguration . 将此留给应用程序本身。(自动)配置类如下所示:

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

书记官长:

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

最后,顾问:

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

后两个类是从spring复制和修改的。顾问是从 MethodSecurityMetadataSourceAdvisor ,这是一个在spring的人可能会看到的类,因为 transient volatile 同步对象(我复制了它,因为我还不能确定它是否应该是 final 相反),因为它有一个从未使用过的私有方法。

相关问题