You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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数据:

  1. 配置SessionRegistry
@Bean
public SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
}

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}
  1. 在SecurityConfig中注册
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // ...其他配置
        .sessionManagement()
            .maximumSessions(1)
            .sessionRegistry(sessionRegistry());
}
  1. 在业务逻辑中更新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 17:45:39