如何将 GlobalMethodSecurityConfiguration 迁移到 Reactive Spring?

Posted

技术标签:

【中文标题】如何将 GlobalMethodSecurityConfiguration 迁移到 Reactive Spring?【英文标题】:How to migrate the GlobalMethodSecurityConfiguration to Reactive Spring? 【发布时间】:2021-01-28 15:51:42 【问题描述】:

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

然后我覆盖了 GlobalMethodSecurityComfiguration.createExpressionHandler(),然后 createSecurityExpressionRoot() 返回了被覆盖的 SecurityExpressionRoot 的实例。 这在 servlet 场景中有效,不幸的是,这似乎不适用于响应式场景。

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

在我的测试中出现以下错误:

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 公共类 MethodSecurityConfig 扩展 GlobalMethodSecurityConfiguration 私有最终 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) 
 

控制器:

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) 

控制器测试方法

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

【问题讨论】:

您是否尝试过在配置类中使用“@EnableReactiveMethodSecurity”代替“@EnableGlobalMethodSecurity”? 是的,问题出在依赖关系上。 【参考方案1】:

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

为了具有相同的功能,您需要将 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();

参考资料:

https://github.com/spring-projects/spring-security/issues/5046#issuecomment-427097710

【讨论】:

以上是关于如何将 GlobalMethodSecurityConfiguration 迁移到 Reactive Spring?的主要内容,如果未能解决你的问题,请参考以下文章

如何将Ios文件上传到

Qt如何将文字变成图片?

如何将Bitmap保存为本地图片文件?

在MATLAB中如何将图导出

ASP如何将SQLSERVER数据导出到DBF(VF)

如何将CSV格式转换成JSON格式