如何仅为用户自己的端点启用请求
How can I enable request only for user's own endpoint
我有一个这样的休息端点:
/users/{userId}/something
我使用 oauth2 实现了身份验证。
我的 WebSecurityConfig 如下所示:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll();
}
如何只允许用户访问他们自己的端点(例如,ID 为 100 的用户只能访问 /users/100/something
)而不能看到另一个端点(如 /users/200/something
)?
这可能吗?
有很多方法可以解决这个问题,但我挑选了三个解决这个问题的方法。
自定义安全表达式
我会推荐一种基于自定义安全性的注释方法。这将涉及实现自定义安全表达式、相关表达式处理程序和方法安全配置。如果这对您来说太麻烦了,下一个方法会稍微简单一些。
public class UserIdentityMethodSecurityExpressionRoot
extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
public UserIdentityMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public boolean userIdentity(Long userId) {
User user = ((UserPrincipal) this.getPrincipal()).getUser();
return user.getId() == userId;
}
}
然后可以使用新创建的安全表达式注释其余端点或服务方法:
@PreAuthorize("userIdentity(#userId)")
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
return resourceService.findOne(id);
}
请注意 userId
必须在 某处 提供,可以是 @PathVariable
或 @RequestParam
。 Spring 安全性随后将检查当前用户是否匹配提供的 userId
和 returns 403
否则。
完整示例可用 here 并且已根据您的目的在该问题中进行了改编。
SpEL
也可以使用SpEL,稍微简单一点:
@PreAuthorize("#userId == principal.getId()")
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
return resourceService.findOne(id);
}
其他注意事项
您也可以自己完成所有工作并更快地获得结果,而无需使用 SecurityContextHolder
.
定义自定义表达式
public static void checkUserIdentity(Long userId) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// user did not provide a token
if(auth == null) {
throw new AccessDeniedException();
}
UserDetails details = (UserDetails) auth.getPrincipal();
if(userId != details.getId()) {
throw new AccessDeniedException();
}
}
并像这样使用它:
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
SecurityUtils.checkUserIdentity(userId)
return resourceService.findOne(id);
}
为什么这行得通?如果您已正确设置 Spring 安全性,SecurityContextHolder
将注入当前主体。默认情况下,身份验证绑定到当前执行线程,如果请求已处理或遇到异常将被重置。
我有一个这样的休息端点: /users/{userId}/something
我使用 oauth2 实现了身份验证。 我的 WebSecurityConfig 如下所示:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll();
}
如何只允许用户访问他们自己的端点(例如,ID 为 100 的用户只能访问 /users/100/something
)而不能看到另一个端点(如 /users/200/something
)?
这可能吗?
有很多方法可以解决这个问题,但我挑选了三个解决这个问题的方法。
自定义安全表达式
我会推荐一种基于自定义安全性的注释方法。这将涉及实现自定义安全表达式、相关表达式处理程序和方法安全配置。如果这对您来说太麻烦了,下一个方法会稍微简单一些。
public class UserIdentityMethodSecurityExpressionRoot
extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
public UserIdentityMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public boolean userIdentity(Long userId) {
User user = ((UserPrincipal) this.getPrincipal()).getUser();
return user.getId() == userId;
}
}
然后可以使用新创建的安全表达式注释其余端点或服务方法:
@PreAuthorize("userIdentity(#userId)")
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
return resourceService.findOne(id);
}
请注意 userId
必须在 某处 提供,可以是 @PathVariable
或 @RequestParam
。 Spring 安全性随后将检查当前用户是否匹配提供的 userId
和 returns 403
否则。
完整示例可用 here 并且已根据您的目的在该问题中进行了改编。
SpEL
也可以使用SpEL,稍微简单一点:
@PreAuthorize("#userId == principal.getId()")
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
return resourceService.findOne(id);
}
其他注意事项
您也可以自己完成所有工作并更快地获得结果,而无需使用 SecurityContextHolder
.
public static void checkUserIdentity(Long userId) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// user did not provide a token
if(auth == null) {
throw new AccessDeniedException();
}
UserDetails details = (UserDetails) auth.getPrincipal();
if(userId != details.getId()) {
throw new AccessDeniedException();
}
}
并像这样使用它:
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
SecurityUtils.checkUserIdentity(userId)
return resourceService.findOne(id);
}
为什么这行得通?如果您已正确设置 Spring 安全性,SecurityContextHolder
将注入当前主体。默认情况下,身份验证绑定到当前执行线程,如果请求已处理或遇到异常将被重置。