Spring Reactive Security:如何实现响应式PermissionEvaluator
这个问题确实戳中了Spring WebFlux方法安全里的一个小空白——官方目前没有提供开箱即用的ReactivePermissionEvaluator,但我们可以通过自定义表达式处理逻辑来实现响应式的权限评估。下面是一套完整的非阻塞实现方案:
1. 定义响应式权限评估接口
首先我们需要自己定义一个响应式的权限评估接口,替代传统阻塞式的PermissionEvaluator:
import org.springframework.security.core.Authentication; import reactor.core.publisher.Mono; import java.io.Serializable; public interface ReactivePermissionEvaluator { Mono<Boolean> hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission); }
2. 实现响应式权限评估逻辑
接下来实现这个接口,这里以响应式数据库查询为例(比如用R2DBC操作数据库),完全遵循非阻塞模型:
import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import java.io.Serializable; @Component public class DatabaseReactivePermissionEvaluator implements ReactivePermissionEvaluator { // 注入响应式的用户权限Repository private final UserPermissionRepository permissionRepository; public DatabaseReactivePermissionEvaluator(UserPermissionRepository permissionRepository) { this.permissionRepository = permissionRepository; } @Override public Mono<Boolean> hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { // 从认证信息中获取当前用户名 String username = authentication.getName(); // 响应式查询数据库,判断用户是否有对应权限 return permissionRepository.existsByUsernameAndTargetIdAndTargetTypeAndPermission( username, targetId, targetType, permission.toString() ); } }
3. 自定义响应式方法安全表达式根
默认的MethodSecurityExpressionRoot是阻塞式的,我们需要创建一个支持响应式的表达式根,集成我们的ReactivePermissionEvaluator:
import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.core.Authentication; import reactor.core.publisher.Mono; import java.io.Serializable; public class ReactiveMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { private final ReactivePermissionEvaluator permissionEvaluator; private Object filterObject; private Object returnObject; private Object target; public ReactiveMethodSecurityExpressionRoot(Authentication authentication, ReactivePermissionEvaluator permissionEvaluator) { super(authentication); this.permissionEvaluator = permissionEvaluator; } // 定义响应式的hasPermission方法,供@PreAuthorize调用 public Mono<Boolean> hasPermission(Serializable targetId, String targetType, Object permission) { return permissionEvaluator.hasPermission(this.authentication, targetId, targetType, permission); } // 实现MethodSecurityExpressionOperations的必要方法 @Override public void setFilterObject(Object filterObject) { this.filterObject = filterObject; } @Override public Object getFilterObject() { return filterObject; } @Override public void setReturnObject(Object returnObject) { this.returnObject = returnObject; } @Override public Object getReturnObject() { return returnObject; } @Override public Object getThis() { return target; } public void setTarget(Object target) { this.target = target; } }
4. 配置响应式方法安全处理器
接下来需要配置Spring Security的方法安全,让它使用我们自定义的表达式根。我们需要继承ReactiveMethodSecurityConfiguration,并重写表达式处理器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.annotation.method.configuration.ReactiveMethodSecurityConfiguration; @Configuration @EnableReactiveMethodSecurity // 启用响应式方法安全 public class ReactiveMethodSecurityConfig extends ReactiveMethodSecurityConfiguration { private final ReactivePermissionEvaluator reactivePermissionEvaluator; public ReactiveMethodSecurityConfig(ReactivePermissionEvaluator reactivePermissionEvaluator) { this.reactivePermissionEvaluator = reactivePermissionEvaluator; } @Override protected MethodSecurityExpressionHandler createExpressionHandler() { return new DefaultMethodSecurityExpressionHandler() { @Override protected SecurityExpressionRoot createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { ReactiveMethodSecurityExpressionRoot root = new ReactiveMethodSecurityExpressionRoot(authentication, reactivePermissionEvaluator); root.setThis(invocation.getThis()); root.setPermissionEvaluator(getPermissionEvaluator()); return root; } }; } }
5. 在控制器中使用响应式@PreAuthorize
现在就可以在WebFlux的控制器里正常使用@PreAuthorize注解,调用我们的响应式hasPermission方法了:
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @RestController public class UserProfileController { @PutMapping("/profiles/{username}") @PreAuthorize("@reactiveMethodSecurityExpressionRoot.hasPermission(#username, 'USER_PROFILE', 'WRITE')") public Mono<Void> updateUserProfile(@PathVariable String username) { // 响应式的更新逻辑 return Mono.empty(); } }
关键说明
- Spring Security的响应式方法安全原生支持表达式返回
Mono<Boolean>,所以我们的自定义hasPermission方法返回Mono是完全兼容的,不需要调用block()破坏非阻塞模型。 - 核心思路是通过自定义
MethodSecurityExpressionHandler和表达式根,把响应式的权限评估逻辑注入到方法安全的表达式体系中。 - 彻底避免了在传统
PermissionEvaluator中调用block()导致的响应式管道阻塞问题,完全贴合WebFlux的设计理念。
内容的提问来源于stack exchange,提问作者Marc-Christian Schulze
相关产品推荐
相关产品推荐

