Spring Security WebAuthN集成JWT时的UserDetailsService指定及认证成功后返回JWT问题
Spring Security WebAuthN集成JWT时的UserDetailsService指定及认证成功后返回JWT问题
看起来你在Spring Security中集成WebAuthN与JWT认证时遇到了两个核心问题:一是WebAuthN无法自动识别自定义的UserDetailsService导致抛出java.lang.IllegalStateException: Missing UserDetailsService Bean异常,二是需要在WebAuthN认证成功后返回JWT令牌而非默认响应。下面我会一步步帮你解决这两个问题:
一、解决WebAuthN无法识别自定义UserDetailsService的问题
WebAuthN默认会尝试自动查找上下文里的UserDetailsService Bean,但当存在多个实现或需要显式指定时,我们可以直接在WebAuthN的配置块中绑定你的自定义实现,避免Bean匹配异常。
修改SecurityConfig中的WebAuthN配置
在securityFilterChain方法的WebAuthN配置段中,添加.userDetailsService()方法指定你的自定义实现:
http.webAuthn(web-> web .allowedOrigins("localhost", "https://app-test.1autokolonna.ru/", "https://server-test.1autokolonna.ru/", "https://app.1autokolonna.ru/", "https://server.1autokolonna.ru/") .rpId("1autokolonna.ru") .rpName("1Autokolonna rely party") .userDetailsService(userDetailsService()) // 显式绑定自定义UserDetailsService );
这样WebAuthN就会明确使用你实现的UserDetailsServiceImpl,不会再抛出缺失Bean的异常。
二、实现WebAuthN认证成功后返回JWT令牌
WebAuthN默认的认证成功逻辑是跳转或返回空响应,我们需要自定义一个认证成功处理器,在认证通过后生成JWT并返回给前端。
步骤1:自定义WebAuthN认证成功处理器
创建一个实现AuthenticationSuccessHandler的类,完成JWT生成与响应写入逻辑:
@Component public class WebAuthnAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final JwtService jwtService; private final ObjectMapper objectMapper; // 注入JWT生成工具和ObjectMapper(假设你已有JwtService用于生成JWT) public WebAuthnAuthenticationSuccessHandler(JwtService jwtService, ObjectMapper objectMapper) { this.jwtService = jwtService; this.objectMapper = objectMapper; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { // 从认证信息中获取用户详情 UserDetails userDetails = (UserDetails) authentication.getPrincipal(); // 生成JWT令牌 String jwtToken = jwtService.generateToken(userDetails); // 构造响应体(可根据业务需求扩展字段,比如用户角色) Map<String, Object> responseBody = new HashMap<>(); responseBody.put("token", jwtToken); responseBody.put("username", userDetails.getUsername()); responseBody.put("roles", userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); // 设置响应格式与状态 response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); objectMapper.writeValue(response.getWriter(), responseBody); } }
步骤2:更新SecurityConfig配置
首先在SecurityConfig中注入自定义的成功处理器,然后调整WebAuthN配置和AuthenticationManager以支持WebAuthN认证:
@Configuration @EnableWebSecurity @EnableMethodSecurity @AllArgsConstructor public class SecurityConfig { JwtAuthFilter jwtAuthFilter; WebAuthnAuthenticationSuccessHandler webAuthnAuthenticationSuccessHandler; // 注入自定义成功处理器 // 以下Bean方法保持原有逻辑不变 @Bean public UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); } @Bean CorsConfigurationSource corsConfigurationSource() { // 你的跨域配置逻辑 } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 调整AuthenticationManager,同时支持用户名密码和WebAuthN认证 @Bean public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) throws Exception { // 用户名密码认证Provider DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider(); daoProvider.setUserDetailsService(userDetailsService); daoProvider.setPasswordEncoder(passwordEncoder); // WebAuthN认证Provider WebAuthnAuthenticationProvider webAuthnProvider = new WebAuthnAuthenticationProvider( WebAuthnConfiguration.builder() .rpId("1autokolonna.ru") .rpName("1Autokolonna rely party") .build(), userDetailsService ); // 同时注册两种认证Provider return new ProviderManager(Arrays.asList(daoProvider, webAuthnProvider)); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.cors(Customizer.withDefaults()).csrf(AbstractHttpConfigurer::disable); http.authorizeHttpRequests(auth -> auth .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("PUBLIC ROUTERS").permitAll() .anyRequest().authenticated()); http.sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.authenticationManager(authenticationManager(userDetailsService(), passwordEncoder())); http.logout(logout -> logout .clearAuthentication(true) .invalidateHttpSession(true) .deleteCookies("JSESSIONID")); http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); http.exceptionHandling(handler -> handler.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.FORBIDDEN))); // 更新WebAuthN配置,绑定成功处理器 http.webAuthn(web-> web .allowedOrigins("localhost", "https://app-test.1autokolonna.ru/", "https://server-test.1autokolonna.ru/", "https://app.1autokolonna.ru/", "https://server.1autokolonna.ru/") .rpId("1autokolonna.ru") .rpName("1Autokolonna rely party") .userDetailsService(userDetailsService()) .successHandler(webAuthnAuthenticationSuccessHandler) // 指定认证成功处理器 ); return http.build(); } }
补充说明
- 你需要确保项目中存在
JwtService类,用于生成JWT令牌(核心逻辑包括设置Claims、签名、过期时间等),这是JWT认证的基础组件,你应该已有类似实现。 - 调整
AuthenticationManager是为了让Spring Security同时支持用户名密码和WebAuthN两种认证方式,避免WebAuthN认证请求找不到对应的处理Provider。
内容来源于stack exchange
相关产品推荐
相关产品推荐

