4.4 了解你的用户

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

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

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

@Data
@Entity
@Table(name="Taco_Order")
public class Order implements Serializable {
    ...
        
    @ManyToOne
    private User user;
    
    ...
}

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

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

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

  • 将主体对象注入控制器方法

  • 将身份验证对象注入控制器方法

  • 使用 SecurityContext 获取安全上下文

  • 使用 @AuthenticationPrincipal 注解的方法

例如,可以修改 processOrder() 来接受 java.security.Principal 作为参数。然后可以使用主体名从 UserRepository 查找用户:

@PostMapping
public String processOrder(@Valid Order order, Errors errors,
                           SessionStatus sessionStatus,
                           Principal principal) {
    ...
    
    User user = userRepository.findByUsername(principal.getName());
    order.setUser(user);
    
    ...
}

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

@PostMapping
public String processOrder(@Valid Order 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 Order 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();

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

最后更新于