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

Flutter Windows端无需google_sign_in包实现Google身份验证方案咨询

Flutter Windows端无google_sign_in包的Google登录实现方案

你的实现思路完全可行,本质是走Google标准OAuth 2.0授权码流程,无需依赖google_sign_in包,Windows、macOS、Linux桌面端均可通用。


前置准备

先到Google Cloud控制台创建OAuth 2.0客户端ID,平台选择桌面应用,重定向URI设置为http://localhost:自定义闲置端口,比如http://localhost:7890,端口需后续和代码里的监听端口保持一致。


核心实现逻辑

  1. 拼接Google授权URL,携带客户端ID、重定向URI、权限scope等参数
  2. 调用系统默认浏览器跳转授权页,用户完成登录授权
  3. 本地启动临时HTTP服务,监听预先设置的端口,捕获Google回调的授权码
  4. 用授权码向Google令牌接口发起请求,兑换access_token、id_token等凭证
  5. 关闭本地临时服务,可选使用access_token请求用户信息接口获取账号资料

实现代码示例

依赖引入

pubspec.yaml中添加如下依赖:

dependencies:
  flutter:
    sdk: flutter
  http: ^1.1.0
  url_launcher: ^6.2.2

完整登录逻辑代码

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';

class GoogleLoginService {
  // 替换为自己申请的桌面应用客户端ID
  static const String clientId = "你的客户端ID.apps.googleusercontent.com";
  // 替换为自己设置的重定向URI,端口要和监听端口一致
  static const String redirectUri = "http://localhost:7890";
  static const int listenPort = 7890;
  // 根据需求调整请求的权限范围
  static const List<String> scopes = [
    'openid',
    'email',
    'profile',
  ];

  Future<Map<String, dynamic>?> signInWithGoogle() async {
    // 1. 拼接授权页URL
    final authUrl = Uri.https('accounts.google.com', '/o/oauth2/v2/auth', {
      'client_id': clientId,
      'redirect_uri': redirectUri,
      'response_type': 'code',
      'scope': scopes.join(' '),
      'access_type': 'offline', // 需要refresh_token时保留该参数
    });

    // 2. 打开系统浏览器跳转授权页
    if (await canLaunchUrl(authUrl)) {
      await launchUrl(authUrl);
    } else {
      debugPrint('无法打开系统浏览器');
      return null;
    }

    // 3. 启动本地服务监听授权回调
    final server = await HttpServer.bind(InternetAddress.loopbackIPv4, listenPort);
    try {
      final request = await server.first;
      final code = request.uri.queryParameters['code'];
      if (code == null) {
        request.response.statusCode = HttpStatus.badRequest;
        request.response.write('授权失败,未获取到授权码');
        await request.response.close();
        return null;
      }

      // 给浏览器返回提示,用户可手动关闭页面
      request.response.statusCode = HttpStatus.ok;
      request.response.write('授权成功,你可以关闭当前页面返回应用');
      await request.response.close();

      // 4. 用授权码兑换访问令牌
      final tokenResponse = await http.post(
        Uri.parse('https://oauth2.googleapis.com/token'),
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body: {
          'client_id': clientId,
          'redirect_uri': redirectUri,
          'grant_type': 'authorization_code',
          'code': code,
        },
      );

      if (tokenResponse.statusCode != 200) {
        debugPrint('获取令牌失败: ${tokenResponse.body}');
        return null;
      }
      final tokenData = jsonDecode(tokenResponse.body);
      final accessToken = tokenData['access_token'];

      // 5. 可选:获取用户基础信息
      final userInfoResponse = await http.get(
        Uri.parse('https://www.googleapis.com/oauth2/v3/userinfo'),
        headers: {'Authorization': 'Bearer $accessToken'},
      );

      if (userInfoResponse.statusCode == 200) {
        final userInfo = jsonDecode(userInfoResponse.body);
        return {
          'token_info': tokenData,
          'user_info': userInfo,
        };
      }
      return null;
    } catch (e) {
      debugPrint('登录过程出错: $e');
      return null;
    } finally {
      // 登录完成后必须关闭本地服务,避免端口占用
      await server.close();
    }
  }
}

注意事项

  • 客户端ID必须选择桌面应用类型,不能选移动端、Web端类型,否则会出现重定向URI不匹配错误
  • 本地监听的端口必须和Google Cloud后台设置的重定向URI端口完全一致,不要使用被系统或其他软件占用的端口
  • 若需要实现免重复登录,可以将返回的refresh_token持久化存储,令牌过期后直接用refresh_token兑换新的access_token即可,无需用户重复授权

内容的提问来源于stack exchange,提问作者Christian Findlay

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.08 03:24:03