我有一个这样的休息端点: / 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
)?
这可能吗?
答案 0 :(得分:2)
有很多方法可以解决此问题,但我已经选择了三种解决方案来解决此问题。
我建议使用基于自定义安全性的注释方法。这将涉及实现自定义安全性表达式,相关的表达式处理程序和方法安全性配置。如果这对您来说太多了,那么下一个方法会更简单。
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 Security将检查当前用户是否与提供的userId
匹配,否则返回403
。
完整示例见here,已针对您的目的在此问题中进行了修改。
您还可以使用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
将注入当前主体。默认情况下,身份验证绑定到当前的执行线程,如果请求已处理或遇到异常,则将重置身份验证。