5.5 了解您的用户

通常,仅仅知道用户已经登录是不够的。通常重要的是要知道他们是谁,这样才能调整他们的体验。

例如,在 OrderController 中,当最初创建绑定到订单表单的 TacoOrder 时,如果能够用用户名和地址预先填充 TacoOrder 就更好了,这样他们就不必为每个订单重新输入它。也许更重要的是,在保存订单时,应该将 TacoOrder 与创建订单的用户关联起来。

为了在 TacoOrder 实体和 User 实体之间实现所需的连接,需要向 TacoOrder 类添加一个新属性:

@Data
@Entity
@Table(name="Taco_Order")
public class TacoOrder implements Serializable {

...

  @ManyToOne
  private User user;

...
}

此属性上的 @ManyToOne 注解表明一个订单属于单个用户,相反,一个用户可能有多个订单。(因为使用的是 Lombok,所以不需要显式地定义属性的访问方法。)

在 OrderController 中,processOrder() 方法负责保存订单。需要对其进行修改,以确定经过身份验证的用户是谁,并调用 Order 对象上的 setUser() 以将 Order 与该用户连接起来。

有几种方法可以确定用户是谁。以下是一些最常见的方法:

  • 将 java.security.Principal 注入控制器方法
  • 将 org.springframework.security.core.Authentication 对象注入控制器方法
  • 使用 org.springframework.security.core.context.SecurityContextHolder 得到 SecurityContext
  • 使用 @AuthenticationPrincipal 注解的方法参数(@AuthenticationPrincipal 在 Spring Security 的 org.springframework.security.core.annotation 名中)

例如,可以修改 processOrder() 来接受 java.security.Principal 类型的参数。然后可以使用从 UserRepository 根据规则名查找用户:

@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
    SessionStatus sessionStatus,
    Principal principal) {

...

  User user = userRepository.findByUsername(
            principal.getName());
  order.setUser(user);

...

}

这可以很好地工作,但是它会将与安全性无关的代码与安全代码一起丢弃。可以通过修改 processOrder() 来减少一些特定于安全的代码,以接受 Authentication 对象作为参数而不是 Principal:

@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
    SessionStatus sessionStatus,
    Authentication authentication) {

...

  User user = (User) authentication.getPrincipal();
  order.setUser(user);

...

}

有了身份验证,可以调用 getPrincipal() 来获取主体对象,在本例中,该对象是一个用户。注意,getPrincipal() 返回一个 java.util.Object,因此需要将其转换为 User。

然而,也许最干净的解决方案是简单地接受 processOrder() 中的用户对象,但是使用 @AuthenticationPrincipal 对其进行注解,以便它成为身份验证的主体:

@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
    SessionStatus sessionStatus,
    @AuthenticationPrincipal User user) {

  if (errors.hasErrors()) {
  return "orderForm";
  }

  order.setUser(user);

  orderRepo.save(order);
  sessionStatus.setComplete();

  return "redirect:/";
}

@AuthenticationPrincipal 的优点在于它不需要强制转换(与身份验证一样),并且将特定于安全性的代码限制为注释本身。当在 processOrder() 中获得 User 对象时,它已经准备好被使用并分配给订单了。

还有一种方法可以识别通过身份验证的用户是谁,尽管这种方法有点麻烦,因为它包含了大量与安全相关的代码。你可以从安全上下文获取一个认证对象,然后像这样请求它的主体:

Authentication authentication =
    SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();

尽管这个代码段充满了与安全相关的代码,但是它与所描述的其他方法相比有一个优点:它可以在应用程序的任何地方使用,而不仅仅是在控制器的处理程序方法中,这使得它适合在较低级别的代码中使用。

results matching ""

    No results matching ""