31

我正在用 ASP.NET MVC 5 开发一个网站(目前使用 RC1 版本)。该站点将使用 Facebook 进行用户身份验证和检索初始配置文件数据。

对于身份验证系统,我使用基于 OWIN 的新 ASP.NET 身份引擎(http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc -5.aspx),因为它极大地简化了与外部提供商进行身份验证的过程。

问题是,一旦用户首次登录,我想从 Facebook 个人资料中获取其电子邮件地址,但此数据不包含在生成的声明中。所以我考虑了这些替代方法来获取地址:

  1. 指示 ASP.NET 身份引擎将电子邮件地址包含在从 Facebook 检索然后转换为声明的数据集中。我不知道这是否可能。

  2. 使用 Facebook 图形 API ( https://developers.facebook.com/docs/getting-started/graphapi ) 通过 Facebook 用户 ID(包含在声明数据中)检索电子邮件地址。但是,如果用户将他的电子邮件地址设置为私人,这将不起作用。

  3. 使用 Facebook 图形 API,但指定“我”而不是 Facebook 用户 ID ( https://developers.facebook.com/docs/reference/api/user )。但是需要访问令牌,而且我不知道如何(或者是否可能)检索 ASP.NET 用于获取用户数据的访问令牌。

所以问题是:

  1. 如何指示 ASP.NET 身份引擎从 Facebook 检索其他信息并将其包含在声明数据中?

  2. 或者,我怎样才能检索生成的访问令牌,以便我可以自己询问 Facebook?

谢谢!

注意:对于身份验证系统,我的应用程序使用基于此 SO 答案中链接的示例项目的代码:https ://stackoverflow.com/a/18423474/4574

4

6 回答 6

29

在 Startup.ConfigureAuth (StartupAuth.cs) 中创建一个新的 Microsoft.Owin.Security.Facebook.AuthenticationOptions 对象,将 FacebookAppId、FacebookAppSecret 和一个新的 AuthenticationProvider 传递给它。您将使用 lambda 表达式向 OnAuthenticated 方法传递一些代码,以将声明添加到包含您从 context.Identity 中提取的值的标识中。默认情况下,这将包括access_token。您必须将电子邮件添加到范围。其他用户属性可从 context.User 获得(例如,请参见底部的链接)。

启动.Auth.cs

// Facebook : Create New App
// https://dev.twitter.com/apps
if (ConfigurationManager.AppSettings.Get("FacebookAppId").Length > 0)
{
    var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
    {
        AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
        AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
        Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
        {
            OnAuthenticated = (context) =>
                {
                    context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
                    context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:email", context.Email, XmlSchemaString, "Facebook"));
                    return Task.FromResult(0);
                }
        }

    };
    facebookOptions.Scope.Add("email");
    app.UseFacebookAuthentication(facebookOptions);
}

在 AccountController 中,我使用外部 cookie 从 AuthenticationManager 中提取 ClaimsIdentity。然后我将它添加到使用应用程序 cookie 创建的身份中。我忽略了任何以“...schemas.xmlsoap.org/ws/2005/05/identity/claims”开头的声明,因为它似乎破坏了登录。

AccountController.cs

private async Task SignInAsync(CustomUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

// Extracted the part that has been changed in SignInAsync for clarity.
    await SetExternalProperties(identity);

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

private async Task SetExternalProperties(ClaimsIdentity identity)
{
    // get external claims captured in Startup.ConfigureAuth
    ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

    if (ext != null)
    {
        var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";
        // add external claims to identity
        foreach (var c in ext.Claims)
        {
            if (!c.Type.StartsWith(ignoreClaim))
                if (!identity.HasClaim(c.Type, c.Value))
                    identity.AddClaim(c);
        } 
    }
}

最后,我想显示任何不是来自地方当局的值。我创建了显示在/Account/Manage 页面上的部分视图 _ExternalUserPropertiesListPartial 。我从 AuthenticationManager.User.Claims 中获取了之前存储的声明,然后将其传递给视图。

AccountController.cs

[ChildActionOnly]
public ActionResult ExternalUserPropertiesList()
{
    var extList = GetExternalProperties();
    return (ActionResult)PartialView("_ExternalUserPropertiesListPartial", extList);
}

private List<ExtPropertyViewModel> GetExternalProperties()
{
    var claimlist = from claims in AuthenticationManager.User.Claims
                    where claims.Issuer != "LOCAL AUTHORITY"
                    select new ExtPropertyViewModel
                    {
                        Issuer = claims.Issuer,
                        Type = claims.Type,
                        Value = claims.Value
                    };

    return claimlist.ToList<ExtPropertyViewModel>();
}

只是为了彻底,观点:

_ExternalUserPropertiesListPartial.cshtml

@model IEnumerable<MySample.Models.ExtPropertyViewModel>

@if (Model != null)
{
    <legend>External User Properties</legend>
    <table class="table">
        <tbody>
            @foreach (var claim in Model)
            {
                <tr>
                    <td>@claim.Issuer</td>
                    <td>@claim.Type</td>
                    <td>@claim.Value</td>
                </tr>
            }
        </tbody>
    </table>
}

工作示例和完整代码在 GitHub 上: https ://github.com/johndpalm/IdentityUserPropertiesSample

我们将不胜感激任何反馈、更正或改进。

于 2013-10-23T18:28:10.617 回答
24

要从 facebook 检索其他信息,您可以在配置 facebook 身份验证选项时指定要包含的范围。可以通过实现提供者的 OnAuthenticated 方法来获取检索到的附加信息,如下所示:

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
    Provider = new FacebookAuthenticationProvider()
    {
        OnAuthenticated = (context) =>
            {
                // All data from facebook in this object. 
                var rawUserObjectFromFacebookAsJson = context.User;

                // Only some of the basic details from facebook 
                // like id, username, email etc are added as claims.
                // But you can retrieve any other details from this
                // raw Json object from facebook and add it as claims here.
                // Subsequently adding a claim here will also send this claim
                // as part of the cookie set on the browser so you can retrieve
                // on every successive request. 
                context.Identity.AddClaim(...);

                return Task.FromResult(0);
            }
    }
};

//Way to specify additional scopes
facebookOptions.Scope.Add("...");

app.UseFacebookAuthentication(facebookOptions);

根据此处的代码,如果 facebook 已发送,我看到电子邮件已被检索并在此处添加为声明。你看不出来吗?

于 2013-09-22T16:24:59.367 回答
22

它对我有用Startup.Auth

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()        {
    AppId = "*",
    AppSecret = "**"
};
facebookOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookOptions);

然后在方法中,ExternalLoginCallback或者ExternalLoginConfirmation您收到电子邮件:

ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var email = ext.Claims.First(x => x.Type.Contains("emailaddress")).Value;
于 2013-12-05T23:40:58.640 回答
5

您需要创建一个实例FacebookAuthenticationOptions并配置Provider. Provider包含一个OnAuthenticated在您登录时触发的事件。

var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
    Provider = new FacebookAuthenticationProvider()
    {
        OnAuthenticated = (context) =>
        {
            context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook"));

            return Task.FromResult(0);
        }
    },

    // You can store these on AppSettings
    AppId = ConfigurationManager.AppSettings["facebook:AppId"],
    AppSecret = ConfigurationManager.AppSettings["facebook:AppSecret"]
};

app.UseFacebookAuthentication(facebookOptions);

在上面的代码中,我正在访问access_tokenbycontext.AccessToken并将其添加到Claims当前登录用户的位置。

要稍后访问此值,您需要执行以下操作:

var owinContext = HttpContext.GetOwinContext();
var authentication = owinContext.Authentication;
var user = autentication.User;
var claim = (user.Identity as ClaimsIdentity).FindFirst("urn:facebook:access_token");

string accessToken;
if (claim != null)
    accessToken = claim.Value;

为了简化这一切,你可以创建一个BaseController并让你的所有东西都Controllers继承自它。

BaseController代码将是:

public class BaseController : Controller
{
    public IOwinContext CurrentOwinContext
    {
        get
        {
            return HttpContext.GetOwinContext();
        }
    }

    public IAuthenticationManager Authentication
    {
        get
        {
            return CurrentOwinContext.Authentication;
        }
    }

    public new ClaimsPrincipal User
    {
        get
        {
            return Authentication.User;
        }
    }

    public ClaimsIdentity Identity
    {
        get
        {
            return Authentication.User.Identity as ClaimsIdentity;
        }
    }

    public string FacebookAccessToken
    {
        get
        {
            var claim = Identity.FindFirst("urn:facebook:access_token");

            if (claim == null)
                return null;

            return claim.Value;
        }
    }
}

然后要在您的代码上获取访问令牌,您只需要访问该属性即可FacebookAccessToken

string accessToken = FacebookAccessToken;

可以检索一些其他值作为

context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:username",
    context.User.Value<string>("username"), ClaimValueTypes.String, "Facebook"));

context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:name",
    context.User.Value<string>("name"), ClaimValueTypes.String, "Facebook"));

请注意,并非所有字段都可用,要获取您需要的电子邮件,您需要该Scope电子邮件。

facebookOptions.Scope.Add("email");

然后访问OnAuthenticated事件作为

context.User.Value<string>("email");
于 2013-12-19T21:27:56.713 回答
2

以下是一些对您有帮助的步骤。我正在写一篇博文,但需要一段时间...... - 在 Fb 提供程序中添加范围并将从 FB 返回的数据添加为声明

app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
        {
            AppId = "",
            AppSecret = "",
            //Scope = "email,user_about_me,user_hometown,friends_about_me,friends_photos",
            Provider = new FacebookAuthenticationProvider()
            {
                OnAuthenticated = async context =>
                {
                    foreach (var x in context.User)
                    {
                        context.Identity.AddClaim(new System.Security.Claims.Claim(x.Key, x.Value.ToString()));
                    }
                    //Get the access token from FB and store it in the database and use FacebookC# SDK to get more information about the user
                    context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
                }
            },
            SignInAsAuthenticationType = "External",
        });         
  • 使用 Access Token 并调用 Facebook C# SDK 获取用户的好友列表

        var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
        var access_token = claimsIdentity.FindAll("FacebookAccessToken").First().Value;
        var fb = new FacebookClient(access_token);
        dynamic myInfo = fb.Get("/me/friends");
        var friendsList = new List<FacebookViewModel>();
        foreach (dynamic friend in myInfo.data)
        {
            friendsList.Add(new FacebookViewModel() { Name = friend.name, ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large" });
            //Response.Write("Name: " + friend.name + "<br/>Facebook id: " + friend.id + "<br/><br/>");
        }
    
于 2013-10-10T18:26:52.837 回答
0

我对所有答案的看法都是几分钱……如果您仍然想自己问 Facebook,那么看看已经存在的Facebook软件包是有意义的。它提供了一些已经实现的强大功能,因此您不必自己重新实现它......您可以在此处找到一些如何在 ASP.NET MVC 应用程序中使用它的示例。

于 2014-03-28T09:28:09.513 回答