我的应用程序需要使用Facebook验证用户。
我应该独立为每个平台实现登录,还是使用手动流程? https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.0 我更喜欢有一个登录流程的单一实现,并在所有平台上使用它。
如何获得Facebook登录流程的单一实现?
更新 (10/24/17): 尽管几年前使用这种方法是可以的,但我现在强烈建议使用本机UI进行身份验证,而不是在此处显示的Webview方法。Auth0是通过使用各种身份提供程序为您的应用程序实现本机UI登录的好方法: https://auth0.com/docs/quickstart/native/xamarin
编辑: 我最终在Github上放了一个 示例
我在Xamarin论坛上发布了一篇回答. 在这里重复一下。
让我们从应用程序的核心开始,即Xamarin.Forms PCL项目。你的App
类将看起来像这样:
namespace OAuth2Demo.XForms
{
public class App
{
static NavigationPage _NavPage;
public static Page GetMainPage ()
{
var profilePage = new ProfilePage();
_NavPage = new NavigationPage(profilePage);
return _NavPage;
}
public static bool IsLoggedIn {
get { return !string.IsNullOrWhiteSpace(_Token); }
}
static string _Token;
public static string Token {
get { return _Token; }
}
public static void SaveToken(string token)
{
_Token = token;
}
public static Action SuccessfulLoginAction
{
get {
return new Action (() => {
_NavPage.Navigation.PopModalAsync();
});
}
}
}
}
首先要注意的是GetMainPage()
方法。这会告诉应用程序在启动时应加载哪个屏幕。
我们还有一个简单的属性和方法,用于存储从授权服务返回的Token
,以及一个简单的IsLoggedIn
属性。
还有一个Action属性;我把它放在这里是为了让平台实现有一种方法来执行Xamarin.Forms导航操作。稍后再详细介绍。
您还会在IDE中看到一些红色,因为我们尚未创建ProfilePage
类。所以,让我们来创建它吧。
在Xamarin.Forms PCL项目中创建一个非常简单的ProfilePage
类。我们甚至不需要对其进行任何花哨的处理,因为那将取决于您的具体需求。为了在此示例中简单起见,它将只包含一个标签:
namespace OAuth2Demo.XForms
{
public class ProfilePage : BaseContentPage
{
public ProfilePage ()
{
Content = new Label () {
Text = "Profile Page",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
};
}
}
}
再次提醒,您的IDE可能会出现红色警报,因为我们好像缺少了BaseContentPage
类。 BaseContentPage
类唯一的目的是确保在用户登录之前无法显示应用程序的任何屏幕。(在此简化演示中,我们只是将用户信息保存到内存中,因此每次运行应用程序时都需要重新登录。在真实的应用程序中,您将把已认证的用户信息存储到设备的密钥链中,这将消除每次启动应用程序时登录的需要。)
在Xamarin.Forms PCL项目中创建一个BaseContentPage
类:
namespace OAuth2Demo.XForms
{
public class BaseContentPage : ContentPage
{
protected override void OnAppearing ()
{
base.OnAppearing ();
if (!App.IsLoggedIn) {
Navigation.PushModalAsync(new LoginPage());
}
}
}
}
这里有几个有趣的事情:
我们正在重写OnAppearing()
方法,这类似于iOS UIViewController中的ViewWillAppear方法。您可以在此处执行任何代码,以便在屏幕出现之前立即运行。
我们在此方法中唯一要做的事情是检查用户是否已登录。如果他们没有登录,则执行模态推送到名为LoginPage
的类。如果您不熟悉模态的概念,它只是一个视图,将用户从正常应用程序流程中取出,以执行某些特殊任务;在我们的情况下,进行登录。
因此,让我们在Xamarin.Forms PCL项目中创建LoginPage
类:
namespace OAuth2Demo.XForms
{
public class LoginPage : ContentPage
{
}
}
等等……为什么这个类没有主体???
由于我们正在使用Xamatin.Auth组件(该组件负责构建和呈现与提供的OAuth2信息兼容的Web视图),因此实际上不希望在我们的LoginPage
类中有任何实现。我知道这看起来很奇怪,但请耐心听我解释。
到目前为止,我们一直在Xamarin.Forms PCL项目中工作。但现在我们需要在iOS项目中提供我们的LoginPage
的平台特定的实现。这就是Renderer的概念发挥作用的地方。
在Xamarin.Forms中,当您想要提供平台特定的屏幕和控件(即不从Xamarin.Forms PCL项目中的抽象页面派生其内容的屏幕)时,可以使用Renderers。
在您的iOS平台项目中创建一个LoginPageRenderer
类:
[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]
namespace OAuth2Demo.XForms.iOS
{
public class LoginPageRenderer : PageRenderer
{
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
var auth = new OAuth2Authenticator (
clientId: "", // your OAuth2 client id
scope: "", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri (""), // the auth URL for the service
redirectUrl: new Uri ("")); // the redirect URL for the service
auth.Completed += (sender, eventArgs) => {
// We presented the UI, so it's up to us to dimiss it on iOS.
App.SuccessfulLoginAction.Invoke();
if (eventArgs.IsAuthenticated) {
// Use eventArgs.Account to do wonderful things
App.SaveToken(eventArgs.Account.Properties["access_token"]);
} else {
// The user cancelled
}
};
PresentViewController (auth.GetUI (), true, null);
}
}
}
}
需要注意以下几点:
在命名空间声明前面的顶部,使用了Xamarin.Forms的DependencyService:[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]
。这并不是最优美的方式因为它不是IoC/DI,但无论如何...它可以工作。这是将我们的LoginPageRenderer
“映射”到LoginPage
的机制。
这是我们实际使用Xamarin.Auth组件的类。这就是OAuth2Authenticator
引用的来源。
一旦登录成功,我们通过App.SuccessfulLoginAction.Invoke();
启动Xamarin.Forms导航。这将带我们回到ProfilePage
。
由于我们在使用iOS平台,所以我们所有的逻辑都在ViewDidAppear()
方法中进行。
在您的Android平台项目中创建一个LoginPageRenderer
类。(请注意,您正在创建的类名与iOS项目中的名称相同,但在Android项目中,PageRenderer继承自Android类而不是iOS类。)
[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]
namespace OAuth2Demo.XForms.Android
{
public class LoginPageRenderer : PageRenderer
{
protected override void OnModelChanged (VisualElement oldModel, VisualElement newModel)
{
base.OnModelChanged (oldModel, newModel);
// this is a ViewGroup - so should be able to load an AXML file and FindView<>
var activity = this.Context as Activity;
var auth = new OAuth2Authenticator (
clientId: "", // your OAuth2 client id
scope: "", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri (""), // the auth URL for the service
redirectUrl: new Uri ("")); // the redirect URL for the service
auth.Completed += (sender, eventArgs) => {
if (eventArgs.IsAuthenticated) {
App.SuccessfulLoginAction.Invoke();
// Use eventArgs.Account to do wonderful things
App.SaveToken(eventArgs.Account.Properties["access_token"]);
} else {
// The user cancelled
}
};
activity.StartActivity (auth.GetUI(activity));
}
}
}
让我们再来看一些有趣的事情:
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))]
这一行代码在命名空间声明之前(非常重要)使用了Xamarin.Forms DependencyService。这与iOS版本的 LoginPageRenderer
没有区别。
同样,这是我们实际使用 Xamarin.Auth 组件的地方,因此 OAuth2Authenticator
的引用也是从那里来的。
就像 iOS 版本一样,一旦登录成功,我们通过 App.SuccessfulLoginAction.Invoke();
触发了一个 Xamarin.Forms 导航,使我们返回到 ProfilePage
。
与 iOS 版本不同的是,我们将所有逻辑都放在 OnModelChanged()
方法中而非 ViewDidAppear()
中。
iOS 版本如下:
...而 Android 版本如下:
更新: 我在我的博客上提供了一个详细的示例:http://www.joesauve.com/using-xamarin-auth-with-xamarin-forms/
DependencyService
或任何其他DI容器进行注入。iOS 8:对于那些正在使用@NovaJoe代码且在视图上卡住的人,请添加以下代码以解决问题:
bool hasShown;
public override void ViewDidAppear(bool animated)
{
if (!hasShown)
{
hasShown = true;
// the rest of @novaJoe code
}
}
Android的PageRenderer的正确实现方式是:
using System;
using Android.App;
using Android.Content;
using OAuth2Demo.XForms.Android;
using Xamarin.Auth;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using XamarinAuth;
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))]
namespace OAuth2Demo.XForms.Android
{
public class LoginPageRenderer : PageRenderer
{
public LoginPageRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
// this is a ViewGroup - so should be able to load an AXML file and FindView<>
var activity = this.Context as Activity;
var auth = new OAuth2Authenticator(
clientId: "<Constants.clientId>", // your OAuth2 client id
scope: "<Constants.scope>", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri("<Constants.authorizeUrl>"), // the auth URL for the service
redirectUrl: new Uri("<Constants.redirectUrl>")); // the redirect URL for the service
auth.Completed += (sender, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
App.SuccessfulLoginAction.Invoke();
// Use eventArgs.Account to do wonderful things
App.SaveToken(eventArgs.Account.Properties["access_token"]);
}
else
{
// The user cancelled
}
};
activity.StartActivity(auth.GetUI(activity));
}
}
}
在@NovaJoe的代码中又有一个补充,在iOS8上使用Facebook,您需要修改Renderer类如下以在成功验证后关闭视图。
auth.Completed += (sender, eventArgs) => {
// We presented the UI, so it's up to us to dimiss it on iOS.
/* 添加这行很重要 */
DismissViewController (true, null);
if (eventArgs.IsAuthenticated) {
App.Instance.SuccessfulLoginAction.Invoke ();
// Use eventArgs.Account to do wonderful things
App.Instance.SaveToken (eventArgs.Account.Properties ["access_token"]);
} else {
// The user cancelled
}
};
OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e);
的不同函数,显然接收不同的参数。 - Mario Galván