我在项目中重写了 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/