SpringBoot+React+Google Auth打包应用重定向过多问题排查与配置迁移
问题:Spring Boot + React OAuth2 打包后重定向过多问题
原示例在前后端分开运行时正常,使用Maven插件将React打包到Spring Boot生成Jar包后,出现重定向过多或过滤器链异常问题,需将旧的WebSecurityConfigurerAdapter配置迁移至新的SecurityFilterChain模式。
现有配置信息
application.properties
spring.security.oauth2.client.registration.google.clientId=4..........com
spring.security.oauth2.client.registration.google.clientSecret=qsl,....Af
spring.security.oauth2.client.registration.google.redirectUri={baseUrl}/oauth2/callback/{registrationId}
spring.security.oauth2.client.registration.google.scope=email,profile
React 常量配置
export const API_BASE_URL = "http://localhost:8080"; export const ACCESS_TOKEN = "accessToken"; export const OAUTH2_REDIRECT_URI = "http://localhost:8080/"; export const GOOGLE_AUTH_URL = API_BASE_URL + "/oauth2/authorization/google?redirect_uri=" + OAUTH2_REDIRECT_URI;
Google 端配置

安全配置迁移需求
旧版基于WebSecurityConfigurerAdapter的配置
import com.grodutch.shop.security.CustomUserDetailsService; import com.grodutch.shop.security.RestAuthenticationEntryPoint; import com.grodutch.shop.security.TokenAuthenticationFilter; import com.grodutch.shop.security.oauth2.CustomOAuth2UserService; import com.grodutch.shop.security.oauth2.HttpCookieOAuth2AuthorizationRequestRepository; import com.grodutch.shop.security.oauth2.OAuth2AuthenticationFailureHandler; import com.grodutch.shop.security.oauth2.OAuth2AuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( securedEnabled = true, jsr250Enabled = true, prePostEnabled = true ) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private CustomOAuth2UserService customOAuth2UserService; @Autowired private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; @Autowired private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler; @Autowired private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; @Bean public TokenAuthenticationFilter tokenAuthenticationFilter() { return new TokenAuthenticationFilter(); } @Bean public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() { return new HttpCookieOAuth2AuthorizationRequestRepository(); } @Override public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder .userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean(BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http .cors() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .csrf() .disable() .formLogin() .disable() .httpBasic() .disable() .exceptionHandling() .authenticationEntryPoint(new RestAuthenticationEntryPoint()) .and() .authorizeRequests() .antMatchers("/", "/error", "/favicon.ico", "/**/*.png", "/**/*.gif", "/**/*.svg", "/**/*.jpg", "/**/*.html", "/**/*.css", "/**/*.js") .permitAll() .antMatchers("/auth/**", "/oauth2/**") .permitAll() .anyRequest() .authenticated() .and() .oauth2Login() .authorizationEndpoint() .baseUri("/oauth2/authorize") .authorizationRequestRepository(cookieAuthorizationRequestRepository()) .and() .redirectionEndpoint() .baseUri("/oauth2/callback/*") .and() .userInfoEndpoint() .userService(customOAuth2UserService) .and() .successHandler(oAuth2AuthenticationSuccessHandler) .failureHandler(oAuth2AuthenticationFailureHandler); http.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } }
新版SecurityFilterChain初始(未完成)配置
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeRequests() .requestMatchers("/sign-in") .permitAll() .anyRequest() .authenticated() .and() .oauth2Login() .loginPage("/sign-in"); return http.build(); }
问题分析与解决方案
重定向循环核心原因
- 静态资源权限缺失:打包后React静态资源(HTML/CSS/JS)被Spring Security拦截,未登录访问首页触发重定向,回调后又回到首页形成循环。
- 关键配置遗漏:新版配置未迁移旧版中的CORS、无状态会话、自定义OAuth2处理器、Token过滤器等核心逻辑,导致认证流程异常。
- RedirectUri硬编码:React中硬编码的
OAUTH2_REDIRECT_URI可能与实际部署环境不匹配,或未在Google端授权。
完整的SecurityFilterChain迁移配置
import com.grodutch.shop.security.CustomUserDetailsService; import com.grodutch.shop.security.RestAuthenticationEntryPoint; import com.grodutch.shop.security.TokenAuthenticationFilter; import com.grodutch.shop.security.oauth2.CustomOAuth2UserService; import com.grodutch.shop.security.oauth2.HttpCookieOAuth2AuthorizationRequestRepository; import com.grodutch.shop.security.oauth2.OAuth2AuthenticationFailureHandler; import com.grodutch.shop.security.oauth2.OAuth2AuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( securedEnabled = true, jsr250Enabled = true, prePostEnabled = true ) public class SecurityConfig { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private CustomOAuth2UserService customOAuth2UserService; @Autowired private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; @Autowired private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler; @Autowired private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; @Bean public TokenAuthenticationFilter tokenAuthenticationFilter() { return new TokenAuthenticationFilter(); } @Bean public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() { return new HttpCookieOAuth2AuthorizationRequestRepository(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .csrf().disable() .formLogin().disable() .httpBasic().disable() .exceptionHandling() .authenticationEntryPoint(new RestAuthenticationEntryPoint()) .and() .authorizeHttpRequests(auth -> auth .requestMatchers("/", "/error", "/favicon.ico", "/**/*.png", "/**/*.gif", "/**/*.svg", "/**/*.jpg", "/**/*.html", "/**/*.css", "/**/*.js") .permitAll() .requestMatchers("/auth/**", "/oauth2/**") .permitAll() .anyRequest() .authenticated() ) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(endpoint -> endpoint .baseUri("/oauth2/authorize") .authorizationRequestRepository(cookieAuthorizationRequestRepository()) ) .redirectionEndpoint(endpoint -> endpoint .baseUri("/oauth2/callback/*") ) .userInfoEndpoint(endpoint -> endpoint .userService(customOAuth2UserService) ) .successHandler(oAuth2AuthenticationSuccessHandler) .failureHandler(oAuth2AuthenticationFailureHandler) ); http.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }
额外配套调整
- React常量动态化:替换硬编码URL,适配不同部署环境
// 动态获取当前域名,避免硬编码 export const OAUTH2_REDIRECT_URI = window.location.origin + "/"; export const GOOGLE_AUTH_URL = "/oauth2/authorization/google?redirect_uri=" + OAUTH2_REDIRECT_URI;
- Google端配置校验:确认授权重定向URI包含当前应用的访问地址(本地/生产域名)
- Spring Boot静态资源映射:在
application.properties添加配置确保静态资源正确加载
spring.web.resources.static-locations=classpath:/static/
内容的提问来源于stack exchange,提问作者Kris Swat
相关产品推荐
相关产品推荐

