Spring授权服务器中LoginUser作为AuthenticationPrincipal为空的配置问题
问题解决方案:@AuthenticationPrincipal 获取LoginUser为null的处理
针对你遇到的OAuth2授权码模式下@AuthenticationPrincipal注解无法获取到LoginUser实例的问题,需要在**Spring Security配置类(SessionAuthSecurityConfig)和授权服务器配置类(AuthorizationServerConfig)**中同时调整配置,以下是具体步骤:
1. 确保CustomDaoAuthenticationProvider正确返回LoginUser
首先要保证自定义认证提供者在认证成功后,返回的Authentication对象中Principal是LoginUser实例:
- 检查
CustomDaoAuthenticationProvider的retrieveUser方法,确保调用CustomUserDetailsService.loadUserByUsername后直接返回LoginUser,不要进行类型转换或包装。 - 示例代码(如果重写retrieveUser):
public class CustomDaoAuthenticationProvider extends DaoAuthenticationProvider { @Override protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { // 直接调用自定义UserDetailsService获取LoginUser UserDetails loadedUser = getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null"); } return loadedUser; // 这里返回的是LoginUser实例 } // 其他自定义校验逻辑... }
2. 调整Spring Security配置类(SessionAuthSecurityConfig)
核心配置点:
- 注册自定义认证提供者,替换默认的
DaoAuthenticationProvider - 开启方法级安全支持,确保
@AuthenticationPrincipal注解生效 - 示例配置:
@Configuration @EnableWebSecurity @EnableMethodSecurity // 开启方法级安全,支持@AuthenticationPrincipal public class SessionAuthSecurityConfig extends WebSecurityConfigurerAdapter { private final CustomUserDetailsService customUserDetailsService; private final PasswordEncoder passwordEncoder; public SessionAuthSecurityConfig(CustomUserDetailsService customUserDetailsService, PasswordEncoder passwordEncoder) { this.customUserDetailsService = customUserDetailsService; this.passwordEncoder = passwordEncoder; } // 注册自定义认证提供者 @Bean public CustomDaoAuthenticationProvider customDaoAuthenticationProvider() { CustomDaoAuthenticationProvider provider = new CustomDaoAuthenticationProvider(); provider.setUserDetailsService(customUserDetailsService); provider.setPasswordEncoder(passwordEncoder); return provider; } // 配置认证管理器使用自定义提供者 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(customDaoAuthenticationProvider()); } // 其他配置:比如登录路径、授权规则等... @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll(); } }
3. 调整授权服务器配置类(AuthorizationServerConfig)
核心配置点:
- 把自定义
UserDetailsService注入到授权服务器端点,确保授权流程能正确加载用户信息 - 可选:配置Token增强器,将
LoginUser的自定义字段写入令牌(如果需要在资源服务器获取) - 示例配置:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private final AuthenticationManager authenticationManager; private final CustomUserDetailsService customUserDetailsService; private final PasswordEncoder passwordEncoder; public AuthorizationServerConfig(AuthenticationManager authenticationManager, CustomUserDetailsService customUserDetailsService, PasswordEncoder passwordEncoder) { this.authenticationManager = authenticationManager; this.customUserDetailsService = customUserDetailsService; this.passwordEncoder = passwordEncoder; } // 客户端配置(根据你的业务需求调整) @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("your-client-id") .secret(passwordEncoder.encode("your-client-secret")) .authorizedGrantTypes("authorization_code") .scopes("read", "write") .redirectUris("http://localhost:8080/callback"); } // 配置授权端点,关联自定义UserDetailsService @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(customUserDetailsService) // 关键:让授权服务器使用自定义用户服务 .tokenEnhancer(tokenEnhancer()); // 可选:增强令牌携带用户自定义信息 } // 令牌增强器示例:将LoginUser的字段写入令牌额外信息 private TokenEnhancer tokenEnhancer() { return (accessToken, authentication) -> { if (authentication.getPrincipal() instanceof LoginUser loginUser) { Map<String, Object> additionalInfo = new HashMap<>(); additionalInfo.put("userId", loginUser.getId()); additionalInfo.put("nickname", loginUser.getNickname()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); } return accessToken; }; } // 授权服务器安全配置 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients(); } }
4. 正确使用@AuthenticationPrincipal注解
在接口方法中直接注入LoginUser,确保注解使用正确:
@GetMapping("/api/user/info") public ResponseEntity<Map<String, Object>> getUserInfo(@AuthenticationPrincipal LoginUser loginUser) { // 此时loginUser应为非null Map<String, Object> userInfo = new HashMap<>(); userInfo.put("username", loginUser.getUsername()); userInfo.put("userId", loginUser.getId()); userInfo.put("enabled", loginUser.isEnabled()); return ResponseEntity.ok(userInfo); }
额外排查点
- 调试时打印
SecurityContextHolder.getContext().getAuthentication().getPrincipal(),确认实际类型是否为LoginUser - 检查
CustomDaoAuthenticationProvider的additionalAuthenticationChecks方法是否抛出异常,导致认证失败 - 如果使用分布式会话,确保
LoginUser实现了序列化接口,避免用户信息丢失
内容的提问来源于stack exchange,提问作者francissoria
相关产品推荐
相关产品推荐

