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

Flutter通过serverAuthCode在Spring后端获取OAuth2.0 Access Token的问题

Google OAuth 登录流程问题解答

问题1:流程是否正确?正确流程是什么?

这个流程是完全正确且推荐的。

直接从Flutter前端向Spring后端发送Access Token确实存在安全隐患:Access Token是用户身份凭证,传输过程中若被拦截,攻击者可直接用它调用Google API或你的业务接口;且Access Token虽有效期短,但泄露后的风险不可忽视。

而serverAuthCode是一次性授权码,仅能用于兑换一次Access Token,即便传输中被拦截,攻击者也无法重复使用,安全性更高。

标准安全流程如下:

  1. Flutter端通过google_sign_in插件引导用户完成Google授权,获取serverAuthCode
  2. Flutter将serverAuthCode发送至你的Spring后端
  3. Spring后端使用serverAuthCode,结合Google Cloud Console中注册的客户端ID、客户端密钥,向Google OAuth服务器请求兑换Access Token和Refresh Token
  4. 后端安全存储Refresh Token(用于后续自动刷新Access Token),生成自身业务令牌(如JWT)返回给Flutter前端,前端后续用该业务令牌访问后端接口

问题2:Spring服务器中通过serverAuthCode获取Access Token的实现方式

方式1:使用Spring Security OAuth2 Client(推荐)

若项目已集成Spring Security,这是最简洁的实现方式:

  1. 在application.yml中配置Google OAuth2客户端信息:
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 你的Google客户端ID
            client-secret: 你的Google客户端密钥
            scope: openid,email,profile # 根据业务需求配置权限范围
        provider:
          google:
            token-uri: https://oauth2.googleapis.com/token
  1. 业务代码中注入相关组件完成令牌兑换:
import org.springframework.security.oauth2.client.endpoint.AuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.stereotype.Service;

@Service
public class GoogleOAuthService {
    private final ClientRegistrationRepository clientRegistrationRepository;
    private final AuthorizationCodeTokenResponseClient tokenResponseClient;

    public GoogleOAuthService(ClientRegistrationRepository clientRegistrationRepository,
                              AuthorizationCodeTokenResponseClient tokenResponseClient) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.tokenResponseClient = tokenResponseClient;
    }

    public String getAccessToken(String serverAuthCode) {
        // 获取Google客户端注册信息
        ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("google");

        // 构建包含serverAuthCode的授权响应
        OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponse.success(serverAuthCode)
                .redirectUri(clientRegistration.getRedirectUri())
                .build();

        // 构建授权请求
        OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
                .clientId(clientRegistration.getClientId())
                .redirectUri(clientRegistration.getRedirectUri())
                .scopes(clientRegistration.getScopes())
                .build();

        OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
        OAuth2AuthorizationCodeGrantRequest grantRequest = new OAuth2AuthorizationCodeGrantRequest(clientRegistration, authorizationExchange);

        // 向Google请求兑换令牌并返回Access Token
        return tokenResponseClient.getTokenResponse(grantRequest).getAccessToken().getTokenValue();
    }
}

方式2:直接使用RestTemplate发送请求

若不想依赖Spring Security OAuth2组件,可直接调用Google令牌接口:

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

public class GoogleOAuthUtil {
    private static final String GOOGLE_TOKEN_URI = "https://oauth2.googleapis.com/token";
    private static final String CLIENT_ID = "你的Google客户端ID";
    private static final String CLIENT_SECRET = "你的Google客户端密钥";
    // 注意:此处redirectUri必须与Flutter端获取serverAuthCode时配置的地址一致
    private static final String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob"; // 或你在Google控制台配置的回调地址

    public static String getAccessToken(String serverAuthCode) {
        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "authorization_code");
        params.add("code", serverAuthCode);
        params.add("client_id", CLIENT_ID);
        params.add("client_secret", CLIENT_SECRET);
        params.add("redirect_uri", REDIRECT_URI);

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
        Map<String, Object> response = restTemplate.postForObject(GOOGLE_TOKEN_URI, request, Map.class);

        return (String) response.get("access_token");
    }
}

注意事项

  • 确保Google Cloud Console中,OAuth 2.0客户端ID已正确配置授权范围和回调地址
  • 客户端密钥需严格保密,绝对不能暴露在前端代码中
  • 兑换成功后,Google会返回access_token、refresh_token、expires_in等信息,建议后端存储refresh_token,用于Access Token过期时自动刷新

内容的提问来源于stack exchange,提问作者노제원

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 19:16:26