Spring Security中DAO修改Customer数据无法即时生效的问题求助
解决Spring Security中DAO修改Customer后需登出才生效的问题
问题场景
我在电商项目中集成Spring Security做用户区分,网站运行基本正常,但遇到以下问题:
- User类与Customer为OneToOne关联,Customer包含id、balance等字段,Product与Customer为ManyToOne关联。
- 通过
@AuthenticationPrincipal注解获取当前登录用户的Customer并修改时,数据能即时在网站显示; - 但通过DAO查询Customer(比如从Product获取所属Customer)修改后,数据库虽即时更新,但代码中的集合与网站状态必须登出再登录才会同步更新。
相关代码片段
Users.java
@Entity @Table(name="users") public class Users { @Id @Column(name="username") private String username; @Column(name="password") private String password; @Column(name="enabled") private boolean isActive; @OneToMany(mappedBy="user") private Set<Authorities> authorities; @OneToOne @JoinColumn(name="customer_id") private Customer customer; }
Product.java
@Entity @Table(name="product") public class Product { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="id") private int id; @Column(name="name") private String productName; @Column(name="description") private String productDescription; @Column(name="category") private String productCategory; @Column(name="cost") private int productCost; @ManyToOne(fetch=FetchType.EAGER) @JoinColumn(name="owner_id") private Customer productOwner; }
Customer.java
@Entity @Table(name="customer") public class Customer { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="id") private int id; @Column(name="balance") private int balance; @Column(name="first_name") private String firstName; @Column(name="last_name") private String lastName; @Column(name="email") private String email; @OneToMany(mappedBy="productOwner", fetch=FetchType.EAGER) private List<Product> ownedProducts; }
控制器代码片段
@Autowired CustomerService customerService; @Autowired ProductService productService; @GetMapping("/showOffer/{offerId}") public String getOffer(@PathVariable int offerId, Model theModel, @AuthenticationPrincipal MyUserDetails user) { Product retrievedProduct = productService.findById(offerId); if (user.getCustomer().getBalance() >= retrievedProduct.getProductCost()) { Customer retrievedProductOwner = retrievedProduct.getProductOwner(); /* 此处修改无法即时生效,需登出登录 */ retrievedProductOwner.setBalance(1000); /* 此处修改即时生效,Java集合同步更新 */ user.getCustomer().setBalance(user.getCustomer().getBalance()-retrievedProduct.getProductCost()); /* 尝试强制更新但无效 */ productService.delete(retrievedProduct.getId()); retrievedProduct.getProductOwner().getOwnedProducts().clear(); retrievedProduct.getProductOwner().setOwnedProducts(productService.listOwnerProducts(retrievedProduct.getProductOwner())); } else { System.out.println("Insufficient funds!"); } return "redirect:/home"; }
核心原因
Spring Security的Authentication对象(包含UserDetails实例)存储在HttpSession中:
- 直接通过
@AuthenticationPrincipal修改的是Session中已存在的Customer实例,修改后会直接反映在后续请求中; - 通过DAO查询到的Customer是从数据库或JPA缓存中获取的新实例,修改后仅更新了数据库,但Session中旧的Customer实例并未同步更新,因此网站显示的还是旧数据,直到登出后Session被销毁,重新登录才会加载新数据。
解决方案
方案1:手动更新Session中的Authentication对象
修改DAO查询的Customer并持久化到数据库后,手动更新SecurityContext中的Authentication,替换为包含最新Customer数据的UserDetails实例。
修改后的控制器代码示例:
@GetMapping("/showOffer/{offerId}") public String getOffer(@PathVariable int offerId, Model theModel, @AuthenticationPrincipal MyUserDetails user) { Product retrievedProduct = productService.findById(offerId); if (user.getCustomer().getBalance() >= retrievedProduct.getProductCost()) { Customer retrievedProductOwner = retrievedProduct.getProductOwner(); // 修改Customer数据 retrievedProductOwner.setBalance(1000); // 持久化到数据库 customerService.save(retrievedProductOwner); // --- 关键:更新Session中的用户数据 --- if (retrievedProductOwner.getId() == user.getCustomer().getId()) { // 创建新的MyUserDetails实例,包含最新的Customer MyUserDetails updatedUser = new MyUserDetails(user.getUser()); updatedUser.setCustomer(retrievedProductOwner); // 获取当前Authentication Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // 创建新的Authentication对象,替换UserDetails Authentication newAuth = new UsernamePasswordAuthenticationToken( updatedUser, auth.getCredentials(), auth.getAuthorities() ); // 更新SecurityContext SecurityContextHolder.getContext().setAuthentication(newAuth); } // 原有业务逻辑 user.getCustomer().setBalance(user.getCustomer().getBalance()-retrievedProduct.getProductCost()); productService.delete(retrievedProduct.getId()); } else { System.out.println("Insufficient funds!"); } return "redirect:/home"; }
方案2:使用SessionRegistry管理用户Session(多用户/集群场景)
如果项目涉及多用户同时操作或集群部署,可使用Spring Security的SessionRegistry主动更新指定用户的Session数据:
- 配置SessionRegistry
@Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); }
- 在SecurityConfig中注册
@Override protected void configure(HttpSecurity http) throws Exception { http // ...其他配置 .sessionManagement() .maximumSessions(1) .sessionRegistry(sessionRegistry()); }
- 在业务逻辑中更新Session
@Autowired private SessionRegistry sessionRegistry; // 修改Customer后执行以下逻辑 customerService.save(retrievedProductOwner); // 获取所有在线用户的Principal List<Object> principals = sessionRegistry.getAllPrincipals(); for (Object principal : principals) { if (principal instanceof MyUserDetails) { MyUserDetails existingUser = (MyUserDetails) principal; if (existingUser.getCustomer().getId() == retrievedProductOwner.getId()) { // 更新UserDetails中的Customer existingUser.setCustomer(retrievedProductOwner); // 刷新Session中的用户数据 sessionRegistry.refreshLastRequest(existingUser); } } }
额外注意事项
- 确保修改Customer后调用
customerService.save()将变更持久化到数据库; - 若使用JPA,可通过
entityManager.refresh(retrievedProductOwner)强制刷新实例,避免一级缓存导致的旧数据问题; - 若修改的是其他用户的Customer,需通过SessionRegistry同步其在线Session数据,当前登录用户的Session无需更新。
内容的提问来源于stack exchange,提问作者Thorvas
相关产品推荐
相关产品推荐

