Flutter实现不依赖Firebase的多平台的Google登录

GoogleConsole配置

官网:

点击创建OAuth2.0客户端,选择对应的应用类型。

推荐的应用类型如下,多个应用类型可以共用一个client_id,也可以考虑创建多个client_id。

平台

应用类型

Android

Web

iOS

iOS

MacOS

iOS

Web

Web

Windows

Web

创建OAuth客户端ID的示例如下:

Web应用需要填写回调地址,地址就是Web的访问地址,其中调试的时候可以在命令行或IDE启动配置中添加配置--web-port 3000来指定端口,重定向的URI只支持http或https的schema。

其中Android端需要根据命令提示生成本地证书的SHA-1指纹,其中需要替换命令行的keystore地址,调试的keystore的默认密码是android。

iOS的只需要填写软件包的ID,即类com.example.xxx。

Flutter项目配置

Web

除了获取client_id之外不需要额外配置。

如果要获取token,则使用如下语句获取,提前需要打开Google平台上的PeopleAPI。

代码语言:javascript代码运行次数:0运行复制
final GoogleSignInAuthentication googleSignInAuthentication = await account!.authentication;

没有开启PeopleAPI就会报错如下:

代码语言:javascript代码运行次数:0运行复制
[17:41:01.638][unknown:?][INFO] [AuthService]: ClientException: {
  "error": {
    "code": 403,
    "message": "People API has not been used in project 488325119723 before or it is disabled. Enable it by visiting .googleapis/overview?project=488325119723 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis/google.rpc.ErrorInfo",
        "reason": "SERVICE_DISABLED",
        "domain": "googleapis",
        "metadata": {
          "consumer": "projects/488325119723",
          "service": "people.googleapis",
          "containerInfo": "488325119723",
          "activationUrl": ".googleapis/overview?project=488325119723",
          "serviceTitle": "People API"
        }
      },
      {
        "@type": "type.googleapis/google.rpc.LocalizedMessage",
        "locale": "en-US",
        "message": "People API has not been used in project 488325119723 before or it is disabled. Enable it by visiting .googleapis/overview?project=488325119723 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."
      },
      {
        "@type": "type.googleapis/google.rpc.Help",
        "links": [
          {
            "description": "Google developers console API activation",
            "url": ".googleapis/overview?project=488325119723"
          }
        ]
      }
    ]
  }
}
, uri=;personFields=photos%2Cnames%2CemailAddresses
===============

在Google Console中找到People API,地址:

启动People API如下:

Android

除了获取client_id之外不需要额外配置。

如果使用应用类型为Android的client_id,则会报错如下:

代码语言:javascript代码运行次数:0运行复制
clientId is not supported on Android and is interpreted as serverClientId. Use serverClientId instead to suppress this warning.

serverClientId就是Web应用类型的client_id,可以参考如下,clientId可以去掉,也可以只设置clientId的值为Web应用类型的client_id。

代码语言:javascript代码运行次数:0运行复制
final GoogleSignIn _googleSignIn = GoogleSignIn(
  clientId: '<ANDROID_CLIENT_ID>',      // 可选,仅Android需要
  serverClientId: '<WEB_CLIENT_ID>',    // 必须,Web应用ID
);

iOS

参考配置如下:

/packages/google_sign_in_ios

在Info.plist文件中添加如下配置,其中需要配置client_id,以及域名反转过来的client_id。

代码语言:javascript代码运行次数:0运行复制
<key>CFBundleURLTypes</key>
<array>
    <dict>
       <key>CFBundleTypeRole</key>
       <string>Editor</string>
       <key>CFBundleURLSchemes</key>
       <array>
          <string>com.googleusercontent.apps.488325119723-xxx</string>
       </array>
    </dict>
</array>

<key>GIDClientID</key>
<string>488325119723-xxx.apps.googleusercontent</string>

MacOS

在Info.plist文件中仍然添加iOS中上述配置。

如果缺少Podfile文件,则可以从其它项目中拷贝过来,也可以尝试build一下。

如果报错最低支持版本,则可以在Podfile修改最低支持版本。

代码语言:javascript代码运行次数:0运行复制
platform :osx, '11.5'

安装依赖

代码语言:javascript代码运行次数:0运行复制
cd macos
pod install

如果报错如下

代码语言:javascript代码运行次数:0运行复制
[!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configurations of the target `Runner` to `Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig` or include the `Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig` in your build configuration (`Runner/Configs/AppInfo.xcconfig`).

[!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configurations of the target `Runner` to `Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig` or include the `Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig` in your build configuration (`Runner/Configs/AppInfo.xcconfig`).

[!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configurations of the target `Runner` to `Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig` or include the `Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig` in your build configuration (`Runner/Configs/AppInfo.xcconfig`).

则在macos/Runner/Configs/AppInfo.xcconfig文件中添加如下内容。

代码语言:javascript代码运行次数:0运行复制
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"

如果Xcode报错如下找不到FlutterInputs.xcfilelist文件,则在命令行中先执行flutter build macos可以自动生成文件。

代码语言:javascript代码运行次数:0运行复制
报错:Unable to load contents of file list: '/Users/ryonluo/Code/flutter_game_mvp/macos/Flutter/ephemeral/FlutterInputs.xcfilelist'

如果没有报错,还是获取不到用户信息,但在Xcode有如下日志。

代码语言:javascript代码运行次数:0运行复制
w_socket_connect [C1.1.1.1:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=142.250.217.74:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.1:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.1 142.250.217.74:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_socket_connect [C1.1.1.2:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=142.250.217.106:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.2:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.2 142.250.217.106:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_socket_connect [C1.1.1.3:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=142.251.215.234:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.3:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.3 142.251.215.234:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_socket_connect [C1.1.1.4:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=142.250.69.202:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.4:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.4 142.250.69.202:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_socket_connect [C1.1.1.5:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=172.217.14.234:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.5:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.5 172.217.14.234:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_socket_connect [C1.1.1.6:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=142.251.33.106:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.6:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.6 142.251.33.106:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_socket_connect [C1.1.1.7:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=142.251.211.234:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.7:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.7 142.251.211.234:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_socket_connect [C1.1.1.8:3] connectx(25 (guarded), [srcif=0, srcaddr=<NULL>, dstaddr=142.251.33.74:443], SAE_ASSOCID_ANY, 0, NULL, 0, NULL, SAE_CONNID_ANY) failed: [1: Operation not permitted]
nw_socket_connect [C1.1.1.8:3] connectx failed (fd 25) [1: Operation not permitted]
nw_socket_connect connectx failed [1: Operation not permitted]
nw_endpoint_flow_failed_with_error [C1.1.1.8 142.251.33.74:443 in_progress socket-flow (satisfied (Path is satisfied), interface: utun4, dns)] already failing, returning
nw_endpoint_flow_failed_with_error [C1.1.1.8 142.251.33.74:443 cancelled socket-flow ((null))] already failing, returning
Connection 1: received failure notification
Connection 1: failed to connect 1:1, reason -1
Connection 1: encountered error(1:1)
Task <DEFF15B9-BAC1-4282-96C3-EE94DB1D4DBF>.<1> HTTP load failed, 0/0 bytes (error code: 1 [1:1])
Task <DEFF15B9-BAC1-4282-96C3-EE94DB1D4DBF>.<1> finished with error [1] Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <DEFF15B9-BAC1-4282-96C3-EE94DB1D4DBF>.<1>, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=1, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <DEFF15B9-BAC1-4282-96C3-EE94DB1D4DBF>.<1>"
), _NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: en0, ipv4, dns}

修改 macos/Runner/DebugProfile.entitlementsRelease.entitlements

代码语言:javascript代码运行次数:0运行复制
<key>com.apple.securitywork.client</key>
<true/>
<key>com.apple.securitywork.server</key>
<true/>

登陆实现

基于google_sign_in实现的多平台Google等登陆如下,支持Android, iOS, MacOS, Web等平台,其中android可以使用web端端client_id,macos可以使用ios端client_id。

如果要获取accessToken,可以打开代码中的注释代码。

代码语言:javascript代码运行次数:0运行复制
import 'package:google_sign_in/google_sign_in.dart';

static const Map<String, String> googleClientId = {
  "web": "488325119723-xxx1.apps.googleusercontent", // web, android
  "windows": "488325119723-xxx2.apps.googleusercontent", //windows
  "ios": "488325119723-xxx3.apps.googleusercontent" // ios. macos
};

// 支持Android, iOS, MacOS, Web等平台
Future<bool> signInWithGoogle() async {
  try {
    logger.i('start google login');
    String platform = getPlatform();
    if(platform == '') {
      logger.e('Unknown platform!');
      return false;
    }
    final GoogleSignIn googleSignIn = GoogleSignIn(
      clientId: OAuthConfig.googleClientId[platform],
      scopes: ['email', 'profile'],
    );

    GoogleSignInAccount? account;
    if(kIsWeb){
      account = await googleSignIn.signIn();
      // account = await _googleSignIn.signInSilently();
    }else {
      account = await googleSignIn.signIn();
    }
    // 获取accessToken
    // final GoogleSignInAuthentication googleSignInAuthentication = await account!.authentication;
    // logger.i('accessToken :${googleSignInAuthentication.accessToken}');

    _userId = account?.id;
    _userName = account?.displayName;
    _userEmail = account?.email;
    _userPicture = account?.photoUrl;
    _isAuthenticated = true;
    logger.i(account);
    logger.i('start google login success');
    return true;
  } catch (error) {
    logger.i('start google login failed');
    logger.i(error.toString());
  }
  return false;
}

String getPlatform() {
  if (kIsWeb || Platform.isAndroid) {
    return "web";
  } else if (Platform.isWindows) {
    return "windows";
  } else if (Platform.isIOS || Platform.isMacOS) {
    return "ios";
  }
  return '';
}

延伸

Windows的Google登陆探索

Windows的应用登陆可以参考Web的登陆,GoogleConsole中的回调地址可以通过nginx重定向为自定义的schema,比如notion://oauth2callback,或者直接中浏览器中输入自定义的schema地址即可拉起应用。

拉起应用需要提前中Windows修改注册表,参考如下:

如果要实现给应用传递参数,在可以参考如下方式传参。

在注册表的 command 键中,%1 会被替换为实际传递给应用程序的参数。当用户点击一个自定义 URL 协议(如 notion://)时,完整的 URL(如 notion://open?page=123)会作为参数传递给应用程序。

代码语言:javascript代码运行次数:0运行复制
notion:/oauth2callback?state=eyJjYWxsYmFja1R5cGUiOiJuYXRpdmVyZWRpcmVjdCIsImVuY3J5cHRlZFRva2VuIjoidjAyOmxvZ2luX3dpdGhfZ29vZ2xlOkhNc3BWMV9kTXZqM0xEX2h2anhQRUN5QkIyVDNXenNPM1BnXzF0SFpVeFJiTUNHZ0ZnQ1dxOGhwQUNhdlN3RnNNWHdrN1MtaEU0LUFtUElpR0ZVeFAxLWk3VkxzeEZiYzNPNk0waEh6ZjdidTVvdXkxQm5FZXdkaDlrSVBHZjVRVWZkOCJ9&code=4%2F0AQSTgQEH6RpPdpU7DuXDGyQrt1JbmERJ0SPZQV0pRuskgqzF13aeIJIbX0v5_bdYHv4V3g&scope=email%20profile%20https%3A%2F%2Fwww.googleapis%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis%2Fauth%2Fuserinfo.profile%20openid&authuser=0&prompt=none&di=99c7d862c0ae4050b14bdf98ebdb6ad1