如何仅为用户自己的端点启用请求

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 将注入当前主体。默认情况下,身份验证绑定到当前执行线程,如果请求已处理或遇到异常将被重置。