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

为何@PreAuthorize中principal是String而非UserDetails类?

问题:@PreAuthorize中Principal为String而非自定义UserDetails类

场景复现

我在方法上标注了@PreAuthorize注解,调用自定义bean做权限校验:

@PreAuthorize("@preAuthUtils.test(authentication)")
public void method(){
    ...
}

自定义bean仅用于打印认证信息和用户主体:

@Component(value = "preAuthUtils")
public class PreAuthUtils {
        public boolean test(UsernamePasswordAuthenticationToken x) {
                System.out.println("type: " + x.getClass().getSimpleName());
                System.out.println("string: " + x.toString());
                final var user = x.getPrincipal();
                System.out.println("type: " + user.getClass().getSimpleName());
                System.out.println("string: " + user.toString());
                return true;
        }
}

请求后输出显示,Principal的类型是String而非我实现了UserDetails接口的DefaultUserDetails:

type: UsernamePasswordAuthenticationToken
string: UsernamePasswordAuthenticationToken [Principal=johndoe, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_USER, ROLE_CUSTOMER]]

type: String
string: johndoe

我需要获取用户ID及相关信息来判断访问权限,却一直无法拿到自定义UserDetails实例,十分困惑。


原因分析

出现这种情况的核心原因是:Spring Security的认证流程中,没有将自定义UserDetails实例设置到Authentication对象的Principal中,常见触发场景包括:

  1. 自定义UserDetailsService的loadUserByUsername方法没有返回DefaultUserDetails实例,而是直接返回了用户名字符串
  2. 未正确配置Spring Security使用自定义的UserDetailsService,导致框架使用默认逻辑只存储用户名作为Principal
  3. 自定义认证过滤器时,认证成功后手动将Principal替换成了字符串

解决步骤

1. 检查并修正UserDetailsService实现

确保loadUserByUsername方法返回的是你的DefaultUserDetails实例,而非字符串:

@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 {
        // 从数据库查询用户完整信息
        UserEntity user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
        
        // 返回自定义DefaultUserDetails,封装用户ID等信息
        return new DefaultUserDetails(
                user.getId(),
                user.getUsername(),
                user.getPassword(),
                AuthorityUtils.createAuthorityList(user.getRoles().toArray(new String[0]))
        );
    }
}

2. 配置Spring Security使用自定义UserDetailsService

在Security配置类中明确指定自定义的UserDetailsService和密码编码器,确保框架使用你的逻辑加载用户:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final CustomUserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    public SecurityConfig(CustomUserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .formLogin(form -> form.permitAll())
                // 绑定自定义UserDetailsService和密码编码器
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
        return http.build();
    }

    // 配置密码编码器示例
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3. 优化PreAuthUtils的参数兼容性

为了适配更多Authentication类型,建议将方法参数改为Authentication接口,再强制转换为自定义UserDetails:

@Component(value = "preAuthUtils")
public class PreAuthUtils {
        public boolean test(Authentication authentication) {
                System.out.println("type: " + authentication.getClass().getSimpleName());
                System.out.println("string: " + authentication.toString());
                
                // 转换为自定义DefaultUserDetails,获取用户ID等信息
                DefaultUserDetails user = (DefaultUserDetails) authentication.getPrincipal();
                System.out.println("type: " + user.getClass().getSimpleName());
                System.out.println("string: " + user.toString());
                System.out.println("用户ID: " + user.getId()); // 这里可以获取自定义属性
                
                return true;
        }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 04:05:12