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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:30:29