5.4 启用方法级别保护

虽然很容易考虑 web 请求级别的安全性,但这并不总是应用安全约束的最好方式。有时最好在用户执行操作时,再验证用户是否经过身份验证,并且具有足够的权限。

例如,假设出于管理目的,有一个服务类包含用于从数据库中清除所有订单的方法。使用注入的 OrderRepository 方法来实现,这可能看起来有点像下面这样:

public void deleteAllOrders() {
  orderRepository.deleteAll();
}

现在,假设有一个 POST 请求控制器调用 deleteAllOrders() 方法:

@Controller
@RequestMapping("/admin")
public class AdminController {

private OrderAdminService adminService;

  public AdminController(OrderAdminService adminService) {
    this.adminService = adminService;
  }

  @PostMapping("/deleteOrders")
  public String deleteAllOrders() {
    adminService.deleteAllOrders();
    return "redirect:/admin";
  }

}

这很容易调整 SecurityConfig,以确保只允许授权的用户执行该 POST 请求,请执行以下操作:

.authorizeRequests()
  ...
  .antMatchers(HttpMethod.POST, "/admin/**")
        .access("hasRole('ADMIN')")
  ....

这很好,可以防止任何未经授权的用户向“/admin/deleteOrders”发送 POST 请求,以导致所有订单从数据库中消失。

但是假设其他一些控制器方法也调用 deleteAllOrders()。您需要添加更多匹配器,以保护其他需要保护的控制器请求。

相反,我们可以直接在 deleteAllOrders() 方法上应用安全性配置,如下所示:

`@PreAuthorize` ("hasRole('ADMIN')")
public void deleteAllOrders() {
  orderRepository.deleteAll();
}

@PreAuthorize 注释采用 SpEL 表达式,如果表达式的计算结果为 false,将不会调用该方法。另一方面,如果表达式的计算结果为 true,那么这个方法就被允许了。在这种情况下,@PreAuthorize 检查用户是否有 ROLE_ADMIN 权限。如果是,则将调用该方法并删除所有订单。否则,它将停止执行。

如果 @PreAuthorize 阻止调用,则 Spring Security 将抛出 AccessDeniedException。这是一个未检查的异常,因此您不需要 捕获它,除非您希望应用一些自定义行为。如果未捕获这个异常,它将向上抛出,最终被 Spring Security 的过滤器捕获并处理。或者使用 HTTP 403 页面,或者重定向到登录页面(如果用户正在登录)。

为了使 @PreAuthorize 工作,您需要启用全局方法安全。为此,您需要使用 @EnableGlobalMethodSecurity 注解安全配置类:

@Configuration
@EnableGlobalMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  ...
}

您会发现 @PreAuthorize 对于大多数方法级安全需求来说是一个有用的注解。但是要知道,还有一个不那么有用的 @PostAuthorize 注解。这个 @PostAuthorize 注解的工作原理与 @PreAuthorize 注解几乎相同,只是在调用并返回目标方法之前,不会计算其表达式的值。这允许表达式考虑该方法的返回值来决定是否允许方法执行。调

例如,假设有一个方法通过其 ID 获取订单,但您希望限制它的使用,管理员或订单所属的用户除外。你可以用 @PostAuthorize 方式:

@PostAuthorize("hasRole('ADMIN') || " +
    "returnObject.user.username == authentication.name")
public TacoOrder getOrder(long id) {
...
}

在本例中,TacoOrder 中的 returnObject 从方法返回。如果用户属性的用户名等于身份验证的 name 属性,则允许。但是,为了知道这一点,需要先执行该方法,以便它能够返回 TacoOrder 对象。

但是等等!如果应用的条件不满足,由于安全性依赖于方法调用的返回值,那如何确保方法不被调用呢?这个先有鸡,还是先有蛋的难题,通过先允许方法调用,然后在表达式返回 false 时抛出 AccessDeniedException 来解决。

results matching ""

    No results matching ""