在 Google Flutter 应用程序中管理社交用户详细信息

Manage social user details in Google Flutter application

我是开发 Flutter 应用程序的初学者,并尝试创建一个用于教育目的的示例应用程序。过去几周我决定在 flutter 中做一个没有内置登录或注册部分的示例应用程序,因为它有社交登录选项,如 Facebook 和 Google。我在网上搜索了很多代码示例,了解如何在 Flutter 应用程序中实现 Facebook 和 Google 身份验证。

我对这个社交登录实现有疑问,可能是因为缺乏移动应用程序架构级别的工作经验。此时我在问自己一个问题 "How to manage social login users in applications"

我有一个解决方案,如果用户可以成功登录,将用户详细信息存储到我们的 (Firebase) 数据库中,检查数据库中是否存在用户电子邮件,如果数据库中没有匹配的条目,它将创建一个新用户数据库或者如果存在更新该特定用户的最后登录日期时间。因为这个应用程序以其他形式显示一些与用户相关的数据,它是在登录用户标识的基础上工作的,所以我需要将登录用户的详细信息存储在数据库中。

您应该将 Flutter 应用分成两部分:

  1. 应用程序的 "main" 部分。它显示用户相关数据。
  2. 在上面签名in/Sign。

逻辑很简单。在应用程序启动时,您检查用户是否已通过身份验证。如果是,请将他导航到主屏幕。如果没有,请向他展示一个注册屏幕。以下是如何使用 Firebase 作为后端来实现它:

final currentUser = await FirebaseAuth.instance.currentUser();
if (currentUser != null){
  // We're good: the user is authenticated.
  // Show him the main screen.
  Navigator.of(context).pushReplacement(MaterialPageRoute(
    builder: (context) => HomeScreen()
  ));
} else {
  // The user is not logged in.
  // Show him the sign in/sign up screen.
  Navigator.of(context).pushReplacement(MaterialPageRoute(
    builder: (context) => SignInScreen()
  ));
}

也许这个例子能满足您的需求:

https://github.com/instaflutter/flutter-login-screen-firebase-auth-facebook-login

如果你需要一些帮助来理解,我可以帮助你!

你应该使用令牌逻辑(google 和 facebook 使用)。

您应该为每个用户生成一个令牌并将其存储在您的数据库中。通过此令牌,您可以管理有关用户的一切。 当你打电话给 facebook authtinicating 时,它会给你一个特定用户的令牌。 当您再次将此令牌发送到 facebook 或 google 时,他们将回复用户的详细信息。 所以你可以像他们一样工作..为用户创建一个令牌并从你的数据库中调用它以获取信息..你可以存储从Facebook响应的令牌并在将来匹配它.. 我回答的目的是去搜索令牌概念,因为它会给你很多好处。

您提出的解决方案已经足够好了。返回的 ID 对于您的应用是唯一的。

使用返回到您应用的 ID 作为您应用中用户的标识符。

在唯一 ID

上查看此

这里只留下 social_login 插件

// Import package
import 'package:social_login/social_login.dart';

// Instantiate it
 final socialLogin = SocialLogin();

//Before calling any methods, set the configuration
socialLogin.setConfig(SocialConfig(
      facebookAppId: FACEBOOK_APP_ID,
      googleWebClientId: GOOGLE_WEB_CLIENT_ID, /*In case a Google tokenId is needed*/
      twitterConsumer: TWITTER_CONSUMER_KEY,
      twitterSecret: TWITTER_CONSUMER_SECRET,
    ));

// Get current logged user
 final FacebookUser facebookUser = await socialLogin.getCurrentFacebookUser();
 final GoogleUser googleUser = await socialLogin.getCurrentGoogleUser();
 final TwitterUser twitterUser = await socialLogin.getCurrentTwitterUser();

//Log in social networks
 final FacebookUser facebookUser = await socialLogin.logInFacebookWithPermissions(FacebookPermissions.DEFAULT);
 final GoogleUser googleUser = await socialLogin.logInGoogle();
 final TwitterUser twitterUser = await socialLogin.logInTwitter();

//Log out from social networks
 await socialLogin.logOutFacebook();
 await socialLogin.logOutGoogle();
 await socialLogin.logOutTwitter();

通过使用 Firebase Authentication SDK (Official Guide) 的内置登录,您将能够获取不同平台的登录信息,例如 Google、Twitter、Facebook 甚至 Github.

您无需编写自己的代码 login/signup,使用 Firebase 的正确功能可以轻松处理所有事情。

之后您将获得这样的登录信息(这是来自电子邮件登录,但 Facebook、Google、Twitter 和 Github 可用):

然后您可以 link 数据库中信息的标识符或用户 UID。

哦,最后但同样重要的是,请记住为登录提供程序设置持久性设置,以便在用户关闭应用程序后保持用户登录。

这个想法很简单。用户使用 facebook/google/instagram 登录并从上述服务中获取 accessToken。此令牌必须与每个需要身份验证的请求一起发送。

使用flutter storage保存持久化令牌:https://pub.dev/packages/flutter_secure_storage

我将post举一个我的 flutter 应用程序的例子,我在其中实现了使用 instagram 的登录。

恢复下面的代码:

  • loginService.dart 负责登录 api 调用并将令牌保存到存储
  • main.dart 是应用程序的第一个视图。如果用户未登录,则会在此处显示登录小部件(LoginWidget 在 login.dart 中)。
  • login.dart是登录页面的视图。有一个按钮可以从 LoginService class
  • 调用函数

代码如下:

~/lib/service/loginService.dart

import 'dart:async';
import 'dart:convert' as JSON;
import 'package:http/http.dart' as http;
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:choose/model/User.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:choose/config.dart' as config;

String apiBaseUrl = config.apiBase;
String apiPort = config.port;

class LoginService {

  String clientID;
  String secretID;
  String redirectURI;
  final FlutterSecureStorage storage = new FlutterSecureStorage();
  final String authKey = "token";

  LoginService(this.clientID, this.secretID, this.redirectURI);

  String get authenticationURI {
    return "https://api.instagram.com/oauth/authorize/?client_id=$clientID&redirect_uri=$redirectURI&response_type=code";
  }

  String get authorizationURI {
    return "https://api.instagram.com/oauth/access_token";
  }

  Future<User> authenticate() async {
    clean();
    FlutterWebviewPlugin _flutterWebviewPlugin = FlutterWebviewPlugin();

    // Stream<String> onCode = await _openServer();
    _flutterWebviewPlugin.launch(
        authenticationURI
    );

    Completer<User> loginCompleter = Completer();

    _flutterWebviewPlugin.onStateChanged.listen((viewState) async {
      String url = viewState.url;
      int indexOfCode = url.lastIndexOf("?code=");

      if(url != null && indexOfCode >= 0 && viewState.type == WebViewState.finishLoad) {
        String code = url.substring(url.indexOf('?code=') + 6, url.length);

        http.Response userResponse = await http.get(
          "${config.endpoints['getUserByCode']}?code=$code",
        );
        if(userResponse.statusCode == 200) {
          User user = User.fromMap(JSON.jsonDecode(userResponse.body));
          saveToken(user.token);
          loginCompleter.complete(user);
        } else {
          loginCompleter.completeError("User not found");
        }
        _flutterWebviewPlugin.close();
      }
    });



    return loginCompleter.future;
  }

  void saveToken(token) async {
    storage.write(key: authKey, value: token);
  }

  void clean() async {
    storage.delete(key: authKey);
  }

  Future<String> getToken() async {
    return storage.read(key: authKey);
  }

  static Future<String> getStaticToken() async {
    FlutterSecureStorage storage = FlutterSecureStorage();
    return storage.read(key: "token");
  }

}

~lib/main.dart

import 'package:choose/friends.dart';
import 'package:choose/model/User.dart';
import 'package:flutter/material.dart';
import 'package:choose/login.dart';
import 'package:choose/service/loginService.dart';
import 'package:web_socket_channel/io.dart';

import 'feed.dart';
import 'notification.dart';
import 'newPost.dart';
import 'config.dart' as config;

void main() => runApp(MyApp());

LoginService loginService = new LoginService(
  "71b818abd18043fb8b7c1833912b62ae",
  "3d9d6f87d2354085a74534c13bdda51f",
  config.endpoints['authorize'],
);



class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
      routes: {
        '/feed': (context) => DefaultTabController(
          length: 2,
          child: FeedWidget()
        ),
        '/newpost': (context) => NewPost(),
        '/notifications': (context) => NotificationWidget(
          channel: IOWebSocketChannel.connect(config.websocketURI)
        ),
        '/friends': (context) => FriendsWidget(),
      }

    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  User user;
  String label = 'Continue with Instagram';

  void openLoginPage() async {

    try {
      User _user = await loginService.authenticate();
      this.openPostPage();
    } catch(e) {
      this.setState(() {
        label = 'Try Again';
      });
    }

  }

  void openPostPage() async {
    print(await loginService.getToken());
    print("token");

    Navigator.pushReplacementNamed(context, '/feed');
  }

  void actionDelegator() {
    if(user != null) {
      return openPostPage();
    } else {
      return openLoginPage();
    }
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Login(loginAction: actionDelegator, user: user, label: label);
  }
}

~/lib/service/login.dart

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:choose/model/User.dart';

class Login extends StatefulWidget {

  Login({this.loginAction, this.user, this.label});

  final loginAction;
  final User user;
  final String label;

  _Login createState() => _Login();

}

class _Login extends State<Login> {

  String imageURI = "https://www.travelcontinuously.com/wp-content/uploads/2018/04/empty-avatar.png";

  @override
  Widget build(BuildContext context) {

    if(widget.user != null && widget.user.profilePicture != '' ) {
      imageURI = widget.user.profilePicture;
    }

    return Container (
      color: Colors.green,
      child: Column (
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          CircleAvatar (
            backgroundColor: Colors.transparent,
            radius: 50.0,
            backgroundImage: NetworkImage(imageURI, scale: 2.0),
          ),

          Container(
            padding: EdgeInsets.all(0.0),
            margin: EdgeInsets.all(20.0),
            color: Colors.transparent,
            child: MaterialButton(
              elevation: 5.0,
              color: Color.fromRGBO(193, 53, 132, 1.0),
              child: FlatButton.icon(
                label: Text(widget.label, style: TextStyle(color: Color.fromRGBO(255,220,128, 1.0), fontWeight: FontWeight.w900, fontSize: 16.0)),
                icon: Icon(FontAwesomeIcons.instagram, color: Color.fromRGBO(255,220,128, 1.0)),
              ),
              onPressed: () async {
                widget.loginAction();
              },
            )
          )
        ],
      ),
    );
  }

}

(有人可能会觉得这有帮助)。为了在 flutter 中轻松进行 oauth,请尝试 visa - https://github.com/e-oj/visa

这是 Facebook 身份验证的示例(它还支持 google、twitch、discord 和 Github):

import 'package:visa/auth-data.dart';
import 'package:visa/fb.dart';

class AuthPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      /// Simply Provide all the necessary credentials
      body: FaceBookAuth().visa.authenticate(
          clientID: '139732240983759',
          redirectUri: 'https://www.e-oj.com/oauth',
          scope: 'public_profile,email',
          state: 'fbAuth',
          onDone: done
      )
    );
  }
}

和“完成”回调:

done(AuthData authData){
  print(authData);

  /// You can pass the [AuthData] object to a 
  /// post-authentication screen. It contaions 
  /// all the user and OAuth data collected during
  /// the authentication process. In this example,
  /// our post-authentication screen is "complete-profile".
  Navigator.pushReplacementNamed(
      context, '/complete-profile', arguments: authData
  );
}