如标题所示,我已经通过使用@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
}
}
注:@Autowired
UserService
用于以被动方式从数据库中提取数据。
接下来,我将@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性地求值。
1条答案
按热度按时间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 defaultJwtAuthenticationConverter
. You could configure anyAbstractAuthenticationToken
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())
.