# 4.4 了解你的用户

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

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

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

```java
@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 查找用户：

```java
@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：

```java
@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 对其进行注解，以便它成为身份验证的主体：

```java
@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 对象时，它已经准备好被使用并分配给订单了。

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

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

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v5/di-4-zhang-spring-an-quan/4.4-le-jie-ni-de-yong-hu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
