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

Spring Boot 3.4.0集成Github OAuth2认证后回调URI 404问题

Github OAuth2认证回调后出现404错误的排查与解决

问题描述

在基于Spring Security的Spring Boot项目中集成Github OAuth2认证,回调URI已与Github端配置一致,但认证完成后返回404未找到错误。已尝试将该URI从Jwt过滤器、静态资源映射中排除,问题仍未解决。

Security配置代码

package eu.tasgroup.applicativo.conf;

import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import eu.tasgroup.applicativo.filter.JwtAuthenticationFilter;
import eu.tasgroup.applicativo.repository.AmministratoriRepository;
import eu.tasgroup.applicativo.repository.ClientiRepository;
import eu.tasgroup.applicativo.repository.PermessiAmministratoriRepository;
import eu.tasgroup.applicativo.security.JwtAuthenticationEntryPoint;
import static org.springframework.security.config.Customizer.withDefaults;


@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final ClientiRepository cr;
    private final AmministratoriRepository ar;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final PermessiAmministratoriRepository pr;
    private CustomerUserDetailsService cs;

    public SecurityConfig(ClientiRepository cr, AmministratoriRepository ar, PermessiAmministratoriRepository pr,
            JwtAuthenticationFilter jwtAuthenticationFilter, CustomerUserDetailsService cs, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) {
        this.cr = cr;
        this.ar = ar;
        this.pr = pr;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.cs = cs;
    }

    @Bean
    @Order(2)
    SecurityFilterChain adminFilterChain(HttpSecurity http, CustomAuthenticationFailureHandler failureHandler,
            CustomAuthenticationSuccessHandler successHandler) throws Exception {
        http.securityMatcher("/admin/**")
                .authorizeHttpRequests(
                        (authorize) -> authorize.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                                .permitAll().requestMatchers("/admin/admin-registrazione").permitAll()
                                .requestMatchers("/admin/admin-form-registrazione").permitAll()
                                .requestMatchers("/admin/**").hasRole("ADMIN"))
                .formLogin((form) -> form.loginPage("/admin/admin-login").failureHandler(failureHandler)
                        .successHandler(successHandler).usernameParameter("email").permitAll())
                
                .logout((logout) -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/admin/admin-logout"))
                        .logoutSuccessUrl("/").invalidateHttpSession(true).clearAuthentication(true).permitAll());
        return http.build();
    }

    @Bean
    @Order(3)
    SecurityFilterChain userFilterChain(HttpSecurity http, CustomAuthenticationFailureHandler failureHandler,
            CustomAuthenticationSuccessHandler successHandler) throws Exception {
        http.securityMatcher("/user/**")
                .authorizeHttpRequests(
                        authorize -> authorize.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                                .permitAll().requestMatchers("/user/user-registrazione").permitAll()
                                .requestMatchers("/user/user-form-registrazione").permitAll()
                                .requestMatchers("/user/**").hasRole("USER"))
                .formLogin(form -> form.loginPage("/user/user-login").usernameParameter("email")
                        .failureHandler(failureHandler).successHandler(successHandler).permitAll())
                .logout(logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/user/user-logout"))
                        .logoutSuccessUrl("/").permitAll());
        return http.build();
    }
    @Bean 
     @Order(1) 
     SecurityFilterChain githubOAuth2FilterChain(HttpSecurity http,  CustomAuthenticationFailureHandler failureHandler,
                CustomAuthenticationSuccessHandler successHandler) throws Exception { 
      http.securityMatcher("/oauth2/**").authorizeHttpRequests( (authorizeRequests) -> authorizeRequests 
        .requestMatchers("/").permitAll()
        .anyRequest().authenticated())
       .oauth2Login(oauth2 -> oauth2
               .failureUrl("/user/user-login")
               .userInfoEndpoint(infoEndpoint ->
               infoEndpoint.userService(cs)))
        .logout(logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/user/user-logout"))
                .logoutSuccessUrl("/").permitAll());
      return http.build(); 
     }

    @Bean
    @Order(4)
    SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/api/**")
                // 启用CORS并禁用CSRF
                .cors(cors -> cors.configurationSource(corsConfigurationSource())).csrf(csrf -> csrf.disable())
                // 设置无状态会话
                .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 处理未授权请求的异常
                .exceptionHandling(handling -> handling.authenticationEntryPoint(jwtAuthenticationEntryPoint))
                // 设置端点权限
                .authorizeHttpRequests(auth -> auth.requestMatchers("/api/login").permitAll().anyRequest().authenticated())
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(new CustomerUserDetailsService(ar, cr, pr));
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

    @Bean
    UrlBasedCorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("http://localhost:4200");  // 使用匹配模式
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }

}

控制台日志

  • Securing GET /oauth2/ Set SecurityContextHolder to anonymous SecurityContext
  • Saved request http://localhost:8080/oauth2/?continue to session Redirecting to http://localhost:8080/oauth2/authorization/todo
  • Securing GET /oauth2/authorization/todo
  • Redirecting to https://github.com/login/oauth/authorize?response_type=code&client_id=Ov23li6ngo5KgTDwUnFn&scope=user&state=xqHtfEOaN6Y4103mTO64Tv-1H9ZIITPivoliIHjixEM%3D&redirect_uri=http://localhost:8080/login/oauth2/code/github
  • GET "/login/oauth2/code/github?code=7dbd4161ce384dca7df2&state=xqHtfEOaN6Y4103mTO64Tv-1H9ZIITPivoliIHjixEM%3D", parameters={masked}

应用配置

spring.security.oauth2.client.registration.todo.client-id=XXXXXXXXXXXXXXXXXXXXXX
spring.security.oauth2.client.registration.todo.client-name=AppName
spring.security.oauth2.client.registration.todo.provider=github
spring.security.oauth2.client.registration.todo.client-secret=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
spring.security.oauth2.client.registration.todo.scope=user
spring.security.oauth2.client.registration.todo.redirect-uri=http://localhost:8080/login/oauth2/code/github

解决方案

核心原因

你的githubOAuth2FilterChain仅配置了匹配/oauth2/**路径,但Github回调的路径是/login/oauth2/code/github,不在该过滤器链的匹配范围内,导致回调请求无法被OAuth2相关逻辑处理,最终返回404。

修复步骤

  1. 修改过滤器链匹配路径
    更新githubOAuth2FilterChain的securityMatcher,同时包含OAuth2授权路径和回调路径:

    @Bean 
    @Order(1) 
    SecurityFilterChain githubOAuth2FilterChain(HttpSecurity http, CustomAuthenticationFailureHandler failureHandler,
            CustomAuthenticationSuccessHandler successHandler) throws Exception { 
        // 同时匹配OAuth2授权和回调路径
        http.securityMatcher("/oauth2/**", "/login/oauth2/**")
            .authorizeHttpRequests(authorizeRequests -> authorizeRequests 
                .requestMatchers("/").permitAll()
                .anyRequest().authenticated())
            .oauth2Login(oauth2 -> oauth2
                .failureUrl("/user/user-login")
                .userInfoEndpoint(infoEndpoint ->
                    infoEndpoint.userService(cs)))
            .logout(logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/user/user-logout"))
                .logoutSuccessUrl("/").permitAll());
        return http.build(); 
    }
    
  2. 验证路径拦截规则
    确认其他过滤器链(如apiFilterChain)的匹配路径不会覆盖/login/oauth2/**,当前你的apiFilterChain仅匹配/api/**,无需调整。

  3. 确认回调URI一致性
    再次核对Github后台配置的回调URI与application.properties中的redirect-uri完全一致,包括协议、端口和完整路径,不能有任何拼写错误。

额外建议

Spring Security OAuth2的默认回调路径格式为/login/oauth2/code/{registrationId},你的registrationId是todo,但配置中写的是github,虽然当前配置的redirect-uri能正常工作,但建议保持registrationId与provider的对应关系,避免后续维护时混淆。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 02:24:52