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

Spring Security 6全局共享认证用户问题:新增商品时User为null

问题分析与解决方案

核心问题原因

  1. @AuthenticationPrincipal注入为null:大概率是自定义User实体未实现UserDetails接口,或Spring Security无法将认证主体转换为你的自定义User类型;也可能是导入了错误的User类(比如Spring Security自带的org.springframework.security.core.userdetails.User而非你的com.project.mesi.entity.User)。
  2. Thymeleaf页面${user}为null:首页可能是手动将用户对象存入了Model,但新增商品页面未做此操作,且未使用Spring Security的Thymeleaf扩展直接获取认证信息。

步骤1:确保自定义User实体实现UserDetails

你的com.project.mesi.entity.User必须实现Spring Security的UserDetails接口,这样登录后认证主体才会是你的自定义User实例:

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.Collections;

@Entity
@Table(name = "user")
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long idUser;
    private String username;
    private String password;

    // 构造方法、getter/setter

    // 实现UserDetails的所有方法,根据业务需求返回对应值
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 若无需权限控制,返回空集合或默认权限
        return Collections.emptyList();
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    // 自定义getIdUser方法
    public Long getIdUser() {
        return idUser;
    }
}

步骤2:配置UserDetailsService

创建自定义UserDetailsService实现类,确保登录时从数据库加载你的自定义User实例:

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在:" + username));
    }
}

并在Spring Security配置中关联这个服务(如果未自动关联):

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, CustomUserDetailsService userDetailsService) throws Exception {
    http
            .userDetailsService(userDetailsService) // 添加这行
            .authorizeHttpRequests((auth) -> auth
                    .requestMatchers("/", "/subscribe").permitAll()
                    .requestMatchers("/add_product").authenticated()
                    .anyRequest().authenticated()
            )
            .formLogin((form) -> form
                    .loginPage("/")
                    .defaultSuccessUrl("/")
                    .loginProcessingUrl("/login")
                    .failureUrl("/?error")
                    .permitAll()
            )
            .logout((logout) -> logout
                    .logoutSuccessUrl("/?logout")
                    .permitAll()
            );
     return http.build();
}

步骤3:修复Controller中的用户获取逻辑

方法一:正确使用@AuthenticationPrincipal

确保导入的是你的自定义User类,而非Spring Security的User:

import com.project.mesi.entity.User; // 确认是这个导入
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...其他导入

@PostMapping(value = "/add_product")
public String addProduct(@Validated @ModelAttribute("product") ProductDto productDto,
                         @AuthenticationPrincipal User userConnected) {
    // 现在userConnected不再为null,直接使用即可,无需再次查询数据库
    productDto.setUser(userConnected);
    productService.save(productDto);

    return "redirect:/";
}

方法二:使用SecurityContextHolder(备选)

如果@AuthenticationPrincipal仍有问题,直接从SecurityContext获取认证信息:

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
// ...其他导入

@PostMapping(value = "/add_product")
public String addProduct(@Validated @ModelAttribute("product") ProductDto productDto) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    User userConnected = (User) auth.getPrincipal(); // 转换为自定义User
    productDto.setUser(userConnected);
    productService.save(productDto);

    return "redirect:/";
}

步骤4:修复Thymeleaf页面的用户显示

不要依赖Model中的${user},改用Spring Security的Thymeleaf扩展直接从认证上下文获取信息:

<li class="nav-item" sec:authorize="isAuthenticated()">
    <p class="nav-link text-body m-0" th:text="'Hello ' + ${#authentication.principal.username} + ' !'"></p>
</li>

(需确保已引入Spring Security的Thymeleaf依赖,若未引入,在pom.xml中添加:)

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>

关键注意事项

  • 检查所有User类的导入,确保没有混淆Spring Security自带的User和你的自定义User。
  • 确保登录逻辑正确,CustomUserDetailsService确实返回了你的自定义User实例,而非用户名字符串。
  • Spring Security的SecurityContext是线程绑定的,整个应用中只要用户已认证,都可以通过SecurityContextHolder获取到认证信息,无需手动共享。

内容的提问来源于stack exchange,提问作者mitsuhiratoe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 05:04:52