You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Reactive Security:如何实现响应式PermissionEvaluator

实现响应式PermissionEvaluator支持Spring WebFlux的@PreAuthorize

这个问题确实戳中了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:11:35