spring-security Spring WebFlux安全预授权最佳实践

m0rkklqb  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(186)

如标题所示,我已经通过使用@EnableWebFluxSecurity@EnableReactiveMethodSecurity在SpringWebFlux应用程序中配置了安全性。
我使用RouterFunction来处理请求路由。下面的代码是针对路由器的:

@Component
public class UserServiceRequestRouter {

    @Autowired
    private UserServiceRequestHandler requestHandler;

    @Bean
    public RouterFunction<ServerResponse> route() {
        //@formatter:off
        return RouterFunctions
            .route(GET("/user/{userId}"), requestHandler::getUserDetails);
        //@formatter:on
    }
}

请求处理程序为:

@Component
public class UserServiceRequestHandler {

    @Autowired
    private UserService userService;

    @PreAuthorize("@userServiceRequestAuthorizer.authorizeGetUserDetails(authentication, #request)")
    public Mono<ServerResponse> getUserDetails(ServerRequest request) {
        //@formatter:off
        return userService.getUserDetails(request.pathVariable("userId"))
            .convert()
            .with(toMono())
            .flatMap(
                (UserDetails userDetails) -> ServerResponse.ok()
                    .contentType(APPLICATION_NDJSON)
                    .body(Mono.just(userDetails), UserDetails.class)
            );
        //@formatter:on
    }
}

注:@AutowiredUserService用于以被动方式从数据库中提取数据。

接下来,我将@Component定义为:

@Component
@SuppressWarnings("unused")
@Qualifier("userServiceRequestAuthorizer")
public class UserServiceRequestAuthorizer {

    public boolean authorizeGetUserDetails(JwtAuthenticationToken authentication, ServerRequest request) {
        // @formatter:off
        if (authentication == null) {
            return false;
        }

        Collection<String> roles = authentication.getAuthorities()
            .stream()
            .map(Objects::toString)
            .collect(Collectors.toSet());

        if (roles.contains("Admin")) {
            return true;
        }

        Jwt principal = (Jwt) authentication.getPrincipal();
        String subject = principal.getSubject();
        String userId = request.pathVariable("userId");

        return Objects.equals(subject, userId);
        // @formatter:on
    }
}

值得注意的是,这里我使用的是Spring OAuth2授权服务器,这就是参数authentication的类型为JwtAuthenticationToken的原因。
应用程序正在按照预期工作。但是我想知道我是否以正确的方式做它,这意味着这是以React的方式做方法级别Authorization的最佳实践吗?
以下是我的堆栈:

  • JDK 17语言
  • 启动 Boot :3.0.0-M4
  • 安全性:6.0.0-M6

如果你能给予我任何建议,我将不胜感激。

更新

正如M.代努姆在为什么我不应该使用hasAuthority("Admin") or principal.subject == #userId的评论中提到的,原因是我提供的授权代码只是为了演示的目的。它可能会变得复杂,即使这种复杂性可能由SpEL管理,我宁愿不为了简单。
另外,问题不是关于使用内联SpEL,而是关于它的React性。我不知道在@PreAuthorize中提到的SpEL是否是React性的!如果它本质上是React性的,那么我可以假设在@PreAuthorize中提到的任何表达式都将被React性地求值。

ewm0tg9j

ewm0tg9j1#

As far as I know, SpEL expressions evaluation is synchronous.
Unless your UserServiceRequestAuthorizer does more than checking access-token claims against static strings or request params and payload, I don't know why this would be an issue: it should be very, very fast.
Of course, if you want to check it against data from DB or a web-service this would be an other story, but I'd say that your design is broken and that this data access should be made once when issuing access-token (and set private claims) rather than once per security evaluation (which can happen several times in a single request).

Side notes

It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
I do not agree with that. It would be the same with any authorization-server (Keycloak, Auth0, Microsoft IdentityServer, ...). You have a JwtAuthenticationToken because you configured a resource-server with a JWT decoder and kept the default JwtAuthenticationConverter . You could configure any AbstractAuthenticationToken instead, as I do in this tutorial .
It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
I join @M.Deinum point of view, writing your security rules in a service, like you do, makes it far less readable than inlining expressions: hard to guess what is checked while reading the expression => one has to quit current source file, open security service one and read the code.
If you refer to the tutorial already linked above , it is possible to enhance security DSL and write stuff like: @PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") to stick to your sample, this would give @PreAuthorize("is(#userId) or isAdmin()) .

相关问题