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