Spring Security 6全局共享认证用户问题:新增商品时User为null
问题分析与解决方案
核心问题原因
- @AuthenticationPrincipal注入为null:大概率是自定义
User实体未实现UserDetails接口,或Spring Security无法将认证主体转换为你的自定义User类型;也可能是导入了错误的User类(比如Spring Security自带的org.springframework.security.core.userdetails.User而非你的com.project.mesi.entity.User)。 - 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
相关产品推荐
相关产品推荐

