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

如何集成React Native谷歌登录与Spring Boot Security OAuth2并获取JWT?

最优集成方案及重定向问题解决

核心结论

  1. 可以实现后端认证后重定向回移动端,但更推荐移动端主导OAuth授权码流程,将授权码传递给后端完成令牌交换与用户认证的方案,这更符合移动端OAuth2最佳实践,实现更简洁。
  2. 两种方案均需在谷歌云控制台配置对应的重定向URI(后端回调地址+移动端Scheme)。

方案一:移动端主导授权流程(推荐)

流程说明

移动端通过expo-auth-session直接向谷歌发起授权请求,拿到授权码后传递给后端;后端使用自身的OAuth2客户端信息与谷歌交换令牌,验证用户身份后生成JWT返回给移动端。

步骤实现

1. 谷歌云控制台配置

  • 添加两个重定向URI:
    • 后端回调地址:https://localhost/oauth2/callback/google
    • 移动端Scheme:myapp://oauth2/callback/google(需与Expo项目配置一致)
  • 为移动端单独创建谷歌OAuth客户端(可选,也可复用Web客户端,但需确保Scheme已配置)。

2. Expo移动端代码

import * as AuthSession from 'expo-auth-session';
import AsyncStorage from '@react-native-async-storage/async-storage';

// 替换为你的谷歌移动端Client ID
const GOOGLE_MOBILE_CLIENT_ID = "your-google-mobile-client-id";
const REDIRECT_URI = AuthSession.makeRedirectUri({ scheme: 'myapp' });

export const startGoogleAuth = async () => {
  const authConfig = {
    clientId: GOOGLE_MOBILE_CLIENT_ID,
    scopes: ['email', 'profile'],
    redirectUri: REDIRECT_URI,
  };

  const authResult = await AuthSession.startAsync(authConfig);

  if (authResult.type === 'success') {
    const { code } = authResult.params;
    // 将授权码传给后端交换JWT
    const response = await fetch('https://your-backend-domain/api/auth/google-login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code })
    });

    const { token } = await response.json();
    // 存储JWT到本地
    await AsyncStorage.setItem('authToken', token);
  }
};

3. Spring Boot后端代码

配置调整(保留原有基础配置,确保clientId/clientSecret正确)
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            clientId: clientIdValue
            clientSecret: clientSecretValue
            redirectUri: "https://localhost/oauth2/callback/{registrationId}"
            scope:
              - email
              - profile
认证接口实现
@RestController
@RequestMapping("/api/auth")
public class GoogleAuthController {

    private final OAuth2AuthorizedClientService clientService;
    private final JwtTokenGenerator jwtGenerator; // 自定义JWT生成工具类
    private final UserRepository userRepo; // 用户数据访问层

    public GoogleAuthController(OAuth2AuthorizedClientService clientService,
                                JwtTokenGenerator jwtGenerator,
                                UserRepository userRepo) {
        this.clientService = clientService;
        this.jwtGenerator = jwtGenerator;
        this.userRepo = userRepo;
    }

    @PostMapping("/google-login")
    public ResponseEntity<AuthResponse> loginWithGoogle(@RequestBody GoogleAuthRequest request) {
        // 获取谷歌客户端注册信息
        ClientRegistration googleClient = clientService.getClientRegistration("google");

        // 构建授权码交换请求
        OAuth2AuthorizationCodeGrantRequest tokenRequest = new OAuth2AuthorizationCodeGrantRequest(
                googleClient,
                new OAuth2AuthorizationCode(request.getCode()),
                URI.create(googleClient.getRedirectUri())
        );

        // 向谷歌交换令牌
        OAuth2AccessTokenResponse tokenResponse = new DefaultAuthorizationCodeTokenResponseClient()
                .getTokenResponse(tokenRequest);

        // 解析ID Token获取用户信息
        Jwt idToken = JwtDecoderFactory.fromOidcIssuerLocation(googleClient.getProviderDetails().getIssuerUri())
                .createDecoder()
                .decode(tokenResponse.getIdToken().getTokenValue());

        String email = idToken.getClaimAsString("email");
        String username = idToken.getClaimAsString("name");

        // 查找或创建用户
        User user = userRepo.findByEmail(email)
                .orElseGet(() -> {
                    User newUser = new User();
                    newUser.setEmail(email);
                    newUser.setUsername(username);
                    return userRepo.save(newUser);
                });

        // 生成后端JWT
        String authToken = jwtGenerator.generateToken(user);
        return ResponseEntity.ok(new AuthResponse(authToken));
    }

    // 请求/响应DTO
    static class GoogleAuthRequest {
        private String code;
        // Getter & Setter
    }

    static class AuthResponse {
        private String token;
        // Getter & Setter
    }
}

方案二:后端动态重定向到移动端(基于UserAgent)

流程说明

移动端请求后端OAuth2授权端点,后端通过UserAgent判断请求来源,动态切换重定向URI为移动端Scheme;用户完成谷歌授权后,谷歌重定向回移动端,移动端将授权码传给后端交换JWT。

步骤实现

1. 谷歌云控制台配置

同方案一,需添加后端回调地址与移动端Scheme。

2. Spring Boot自定义授权请求解析器

@Component
public class MobileAuthRequestResolver implements OAuth2AuthorizationRequestResolver {

    private final DefaultOAuth2AuthorizationRequestResolver delegateResolver;

    public MobileAuthRequestResolver(ClientRegistrationRepository repo, String authBaseUri) {
        this.delegateResolver = new DefaultOAuth2AuthorizationRequestResolver(repo, authBaseUri);
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
        OAuth2AuthorizationRequest requestObj = delegateResolver.resolve(request);
        return customizeRedirectUri(request, requestObj);
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientId) {
        OAuth2AuthorizationRequest requestObj = delegateResolver.resolve(request, clientId);
        return customizeRedirectUri(request, requestObj);
    }

    private OAuth2AuthorizationRequest customizeRedirectUri(HttpServletRequest request, OAuth2AuthorizationRequest authRequest) {
        if (authRequest == null) return null;

        // 判断是否为移动端请求
        String userAgent = request.getHeader("User-Agent");
        boolean isMobile = userAgent != null && (userAgent.contains("Mobile") || userAgent.contains("Android") || userAgent.contains("iOS"));

        if (isMobile && "google".equals(authRequest.getClientRegistration().getRegistrationId())) {
            return OAuth2AuthorizationRequest.from(authRequest)
                    .redirectUri("myapp://oauth2/callback/google")
                    .build();
        }
        return authRequest;
    }
}

3. Spring Security配置

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, MobileAuthRequestResolver authResolver) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .oauth2Login(oauth2 -> oauth2
                .authorizationEndpoint(endpoint -> endpoint
                        .authorizationRequestResolver(authResolver)
                )
                .redirectionEndpoint(endpoint -> endpoint.baseUri("/oauth2/callback/*"))
        );
    return http.build();
}

4. Expo移动端代码

import * as AuthSession from 'expo-auth-session';
import AsyncStorage from '@react-native-async-storage/async-storage';

export const startGoogleAuth = async () => {
  const redirectUri = AuthSession.makeRedirectUri({ scheme: 'myapp' });
  // 请求后端授权端点,后端会重定向到谷歌
  const authResult = await AuthSession.startAsync({
    authUrl: `https://your-backend-domain/oauth2/authorize/google?redirect_uri=${encodeURIComponent(redirectUri)}`
  });

  if (authResult.type === 'success') {
    const { code } = authResult.params;
    // 交换JWT
    const response = await fetch('https://your-backend-domain/api/auth/exchange-token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code, redirectUri })
    });

    const { token } = await response.json();
    await AsyncStorage.setItem('authToken', token);
  }
};

关键注意事项

  • CORS配置:后端需允许移动端域名/IP的跨域请求。
  • Expo Scheme配置:在app.json中添加"scheme": "myapp",确保重定向能唤起App。
  • 安全校验:后端交换授权码时,必须验证授权码的有效性,确保重定向URI与谷歌控制台配置一致;所有请求需通过HTTPS传输。

内容的提问来源于stack exchange,提问作者Lucas Soltz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 15:45:23