如何集成React Native谷歌登录与Spring Boot Security OAuth2并获取JWT?
最优集成方案及重定向问题解决
核心结论
- 可以实现后端认证后重定向回移动端,但更推荐移动端主导OAuth授权码流程,将授权码传递给后端完成令牌交换与用户认证的方案,这更符合移动端OAuth2最佳实践,实现更简洁。
- 两种方案均需在谷歌云控制台配置对应的重定向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
相关产品推荐
相关产品推荐

