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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 05:40:54