Spring Boot/React集成Google OAuth2遇CORS问题,无法获取后端JWT求助
我正在开发一个基于React frontend与Spring Boot backend的应用,目标是使用包含角色信息的JWT保护Spring Boot server的特定接口。
当前配置情况
- Google OAuth2登录:用户通过Google登录后,控制台可获取Google提供的JWT,但无法从后端应用获取包含角色信息的自定义JWT。
Spring Boot配置
SecurityFilterChain已配置为允许CORS、禁用CSRF,除登录和OAuth2路由外,所有请求需认证。使用自定义OAuth2UserService,登录成功后重定向至/token接口。
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .cors(cors -> cors.configurationSource(corsConfigurationSource())) // Enable CORS .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/", "/login/**", "/oauth2/**").permitAll() .requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", "/v2/api-docs/**").permitAll() .anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) .defaultSuccessUrl("/token", true)) .build(); } @Bean public UrlBasedCorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowCredentials(true); corsConfiguration.addAllowedOrigin("http://localhost:3000"); // Allow your frontend origin corsConfiguration.addAllowedOrigin("http://127.0.0.1:3000"); // Allow your frontend origin corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); return source; }
用户控制器
/token接口用于生成并返回包含用户角色信息的JWT。
@RestController @RequiredArgsConstructor public class UserController { private final JwtUtils jwtTokenUtil; @GetMapping("/token") public LoginResponse token(@AuthenticationPrincipal OAuth2User oAuth2User) { LoginResponse loginResponse = new LoginResponse(); loginResponse.setToken(jwtTokenUtil.createToken(oAuth2User.getAttribute("email"))); return loginResponse; } }
CORS配置
已配置CORS允许来自React前端地址http://localhost:3000的请求。
前端实现
使用@react-oauth/google实现Google认证,登录成功后尝试将Google JWT发送至后端以获取自定义JWT。
axiosInstance.get('/token', { headers: { 'Authorization': `Bearer ${credentialResponse}` } }) .then(response => { Cookies.set('access_token', response.data.jwt); console.log('Login Successful'); }) .catch(error => { console.log('Login Failed Inner'); console.log(error); });
问题现象
通过前端应用登录时遭遇CORS问题。
具体错误
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=MYCLIENTID&scope=profile%20email&state=...&redirect_uri=http://localhost:8080/login/oauth2/code/google' (redirected from 'http://localhost:8080/token') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
同时收到ERR_FAILED 302 (Found)错误,axios请求因网络错误失败。
补充信息
- Google Cloud Console中的重定向URI与application.properties文件中的配置一致(http://localhost:8080/login/oauth2/code/google)。
- 已在package.json中添加"proxy": "http://localhost:8080"配置。
- 仅测试后端时,/token接口工作正常:用户可保存至数据库,且能获取正确JWT。
咨询问题
如何解决该CORS issue,并在Google login成功后成功获取JWT from my backend?当前配置或实现方式是否存在导致问题的错误?
核心问题分析
前后端OAuth2模式不匹配:
- 后端采用授权码模式(oauth2Login),依赖会话(Session)维护用户认证状态,适合传统服务器端渲染场景。
- 前端使用@react-oauth-google,属于客户端ID令牌模式,直接获取Google的ID Token,是无会话的SPA场景。
当前端发送Google的JWT到/token接口时,后端因无会话会重定向到Google登录页面,而Google接口不会添加前端Origin的CORS头,触发跨域错误。
步骤1:调整Spring Boot为OIDC资源服务器模式
修改SecurityFilterChain,配置为验证Google的ID Token,而非依赖会话式登录:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorize -> authorize // 允许前端访问/token接口获取自定义JWT .requestMatchers("/token").permitAll() .requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**").permitAll() // 其他接口需要自定义JWT认证 .anyRequest().authenticated()) // 配置OIDC资源服务器,验证Google的ID Token .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))) .build(); } // 自定义JWT转换器,提取Google ID Token中的用户信息 private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 可选:从Google JWT的claims中提取角色,或自定义角色逻辑 grantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return converter; }
同时在application.properties中添加Google OIDC配置:
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://accounts.google.com spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://www.googleapis.com/oauth2/v3/certs
步骤2:修改/token接口逻辑
后端已能验证Google的ID Token,调整UserController从JWT中提取用户信息生成自定义JWT:
@RestController @RequiredArgsConstructor public class UserController { private final JwtUtils jwtTokenUtil; @GetMapping("/token") public LoginResponse token(@AuthenticationPrincipal Jwt jwt) { // 从Google的JWT中获取用户邮箱 String email = jwt.getClaim("email"); // 可添加逻辑:查询数据库获取用户角色,或默认分配角色 LoginResponse loginResponse = new LoginResponse(); loginResponse.setToken(jwtTokenUtil.createToken(email)); return loginResponse; } }
步骤3:前端调整(确保正确发送Token)
确认前端发送的是Google返回的credential字段(即ID Token),并匹配后端响应字段:
// 假设Google登录返回的响应中,credential字段是ID Token axiosInstance.get('/token', { headers: { 'Authorization': `Bearer ${credentialResponse.credential}` } }) .then(response => { Cookies.set('access_token', response.data.token); // 后端用setToken,响应字段为token console.log('Login Successful'); }) .catch(error => { console.log('Login Failed Inner'); console.log(error); });
步骤4:验证CORS配置
现有CORS配置已允许localhost:3000,需确保:
- 前端proxy配置生效,请求直接写
/token即可,无需完整域名 - 后端
setAllowCredentials(true)配置正确,前端请求需开启withCredentials: true(axios实例默认可配置)
额外注意事项
- 移除后端原有的
oauth2Login配置,无需后端处理跳转式登录 - Google Cloud Console中,OAuth2客户端ID需添加正确的JavaScript来源(http://localhost:3000),而非仅配置重定向URI
- 自定义JWT工具类(JwtUtils)需正确生成包含角色信息的Token,后续接口用该Token做认证
内容的提问来源于stack exchange,提问作者Emma Johnson

