spring-boot - 如何将 GlobalMethodSecurityConfiguration 迁移到 Reactive Spring?

标签 spring-boot spring-webflux spring-security-oauth2

我在项目中重写了 SecurityExpressionRoot,公开了一种验证当前用户是否有权访问给定资源的方法。

然后我重写了 GlobalMethodSecurityComfiguration.createExpressionHandler() ,然后 createSecurityExpressionRoot() 返回重写的 SecurityExpressionRoot 的实例。 这在 servlet 场景中有效,不幸的是,这似乎在响应式(Reactive)场景中不起作用。

如何将下面的方法安全设置转换为响应式(Reactive)场景?

在我的测试中,我收到以下错误:

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

            at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:379) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]

            Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:

Error has been observed at the following site(s):

            |_ checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.csrf.CsrfWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]

            |_ checkpoint ⇢ org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$MutatorFilter [DefaultWebFilterChain]

            |_ checkpoint ⇢ HTTP GET "/things/1/jobs/1/log" [ExceptionHandlingWebHandler]

@配置 @EnableGlobalMethodSecurity(prePostEnabled = true) @RequiredArgsConstructor 公共(public)类 MethodSecurityConfig 扩展 GlobalMethodSecurityConfiguration { 私有(private)最终 ThingsRepository thingsRepository;

@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
    return new DefaultMethodSecurityExpressionHandler() {
        @Override
        protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
            DeployPermissionSecurityExpressionRoot root = new ThingsPermissionSecurityExpressionRoot(thingsRepository, authentication);
            root.setThis(invocation.getThis());
            root.setPermissionEvaluator(getPermissionEvaluator());
            root.setTrustResolver(getTrustResolver());
            root.setRoleHierarchy(getRoleHierarchy());
            return root;
        }
    };
}

我的安全配置:

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    // @formatter:off
    http
        .csrf().disable()
        .authorizeExchange()
            .anyExchange().authenticated()
        .and()
        .oauth2ResourceServer()
            .jwt(jwt ->{
                 jwt.jwtDecoder(jwtDecoder());
                 jwt.jwtAuthenticationConverter(customJwtAuthConverter());
            })
        .and()
        .securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
    // @formatter:on
    return http.build();
}

SecurityExpressionRoot 实现:

class ThingsPermissionSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private final ThingsRepository ThingsRepository;
    private Object filterObject;
    private Object returnObject;
    private Object target;

    ThingsPermissionSecurityExpressionRoot(ThingsRepository thingsRepository, Authentication authentication) {
        super(authentication);
        this.thingsRepository = thingsRepository;
    }

    public boolean hasThingsWritePrivilege(Long thingsId) {
 

Controller :

public class ThingsController {

    private final JobPublisherProvider jobPublisherProvider;

    @GetMapping("{thingsId}/jobs/{jobId}/log")
    @PreAuthorize("hasThingsWritePrivilege(#thingsId)")
    public Flux<DataBuffer> retrieveJobLog(@PathVariable String thingsId, @PathVariable int jobId) {

Controller 测试方法

@Test
@WithMockUser(roles = {"ROLE_JAR_W"})
public void logValueProperlyRetrieved() {

最佳答案

无法在响应式(Reactive) Spring 应用程序中定义 GlobalSecurityConfiguration(当前 Spring Security 版本 5.5.2)。

为了具有相同的功能,您需要将 ReactiveMethodSecurityConfiguration 中定义的 methodSecurityExpressionHandler 替换为您自己的方法安全表达式处理程序。

为此,您可以扩展 DefaultMethodSecurityExpressionHandler 并将其定义为 @Primary bean。
对于您的情况,如下所示。

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {

  @Override
  protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
    DeployPermissionSecurityExpressionRoot root = new ThingsPermissionSecurityExpressionRoot(thingsRepository, authentication);
    root.setThis(invocation.getThis());
    root.setPermissionEvaluator(getPermissionEvaluator());
    root.setTrustResolver(getTrustResolver());
    root.setRoleHierarchy(getRoleHierarchy());
    root.setDefaultRolePrefix(getDefaultRolePrefix());
    return root;
  }
}

@Bean
@Primary
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
  return new CustomMethodSecurityExpressionHandler();
}

引用文献:

关于spring-boot - 如何将 GlobalMethodSecurityConfiguration 迁移到 Reactive Spring?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64349940/

相关文章:

spring-boot - 使用gradle将Zip文件上传到nexus

java - Spring Boot 验证码配置

java - Spring boot webflux应用程序不会启动IllegalStateException

java - Spring Boot OAuth 资源服务器代理配置

java - Spring boot RestTemplate 发布 400 错误

java - 如何运行打包在spring boot jar中的主类

java - 什么是分组通量以及我们如何使用分组通量?

java - Flatmap 操作后访问 Map 操作内的 Mono 对象

Spring OAuth + JWT --/oauth/token

mysql - Spring boot Oauth2 中拒绝 401 未经授权的访问