Flutter web google_sign_in:如何检索 refreshToken

Flutter web google_sign_in: How to retrieve refreshToken

google_sign_in 没有 return refreshToken。有没有一种方法可以使用 Google 登录并接收可以发送到 API 以进一步访问用户数据的刷新令牌?

刷新令牌也可以通过serverAuthCode获得,目前始终为null。已创建多个问题来描述此问题:

有什么方法可以通过 Google 登录并接收 refreshToken 或 serverAuthCode 进行身份验证?

Google登录基于oAuth2,可以创建自己的流程实现。

这是可用于检索 refreshToken 的 google 登录服务的代码片段:

import 'dart:async';
import 'dart:html' as html;

import 'package:oauth2/oauth2.dart' as oauth2;

class GoogleSignInService {
  final authorizationEndpoint =
      Uri.parse('https://accounts.google.com/o/oauth2/v2/auth');
  final tokenEndpoint = Uri.parse('https://oauth2.googleapis.com/token');

  final String identifier;
  final String secret;
  final String baseUrl;
  final List<String> scopes;

  _SignInSession? _signInSession;

  Uri get redirectUrl => Uri.parse('$baseUrl/callback.html');

  GoogleSignInService({
    required this.identifier,
    required this.secret,
    required this.baseUrl,
    required this.scopes,
  }) {
    html.window.addEventListener('message', _eventListener);
  }

  void _eventListener(html.Event event) {
    _signInSession?.completeWithCode((event as html.MessageEvent).data);
  }

  Future<GoogleSignInUser?> signIn() async {
    if (_signInSession != null) {
      return null;
    }

    final grant = oauth2.AuthorizationCodeGrant(
      identifier,
      authorizationEndpoint,
      tokenEndpoint,
      secret: secret,
    );

    var authorizationUrl = grant.getAuthorizationUrl(
      redirectUrl,
      scopes: scopes,
    );

    final url =
        '${authorizationUrl.toString()}&access_type=offline&prompt=select_account+consent';
    _signInSession = _SignInSession(url);
    final code = await _signInSession!.codeCompleter.future;

    if (code != null) {
      final client = await grant.handleAuthorizationResponse({'code': code});
      return GoogleSignInUser(
        accessToken: client.credentials.accessToken,
        refreshToken: client.credentials.refreshToken,
        idToken: client.credentials.idToken!,
      );
    } else {
      return null;
    }
  }
}

class GoogleSignInUser {
  final String accessToken;
  final String? refreshToken;
  final String idToken;

  const GoogleSignInUser({
    required this.accessToken,
    required this.refreshToken,
    required this.idToken,
  });

  @override
  String toString() {
    return 'GoogleSignInUser{accessToken: $accessToken, refreshToken: $refreshToken, idToken: $idToken}';
  }
}

class _SignInSession {
  final codeCompleter = Completer<String?>();
  late final html.WindowBase _window;
  late final Timer _timer;

  bool get isClosed => codeCompleter.isCompleted;

  _SignInSession(String url) {
    _window =
        html.window.open(url, '_blank', 'location=yes,width=550,height=600');
    _timer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
      if (_window.closed == true) {
        if (!isClosed) {
          codeCompleter.complete(null);
        }
        _timer.cancel();
      }
    });
  }

  void completeWithCode(String code) {
    if (!isClosed) {
      codeCompleter.complete(code);
    }
  }
}

确保同时创建 web/callback.html 文件:

<html>
<body>
</body>
<script>
        function findGetParameter(parameterName) {
            var result = null,
            tmp = [];
            location.search
                .substr(1)
                .split("&")
                .forEach(function (item) {
                    tmp = item.split("=");
                    if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
                 });
            return result;
         }
        let code = findGetParameter('code');

        window.opener.postMessage(code, "http://localhost:5000");
        window.close();
    </script>
</html>

http://localhost:5000 更改为您在生产中使用的任何域。

示例用法:

final googleSignIn = GoogleSignInService(
        identifier: 'CLIENT_ID',
        secret: 'CLIENT_SECRET',
        baseUrl: 'http://localhost:5000',
        scopes: [
          'email',
        ],
      ),
final user = await googleSignIn.signIn();