我正在 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个人资料中获取其电子邮件地址,但此数据不包括在生成的声明中。所以我考虑了这些替代方案来获取地址:
-
指示 ASP.NET 标识引擎将电子邮件地址包含在从 Facebook 检索然后转换的数据集到索赔。我不知道这是否可能。
-
使用 Facebook 图形 API(https://developers.facebook.com/docs/getting-started/graphapi) 至使用 Facebook 用户 ID(即包含在索赔数据中)。但是,如果用户有将他的电子邮件地址设置为私密。
-
使用 Facebook 图形 API,但指定"我"而不是脸书用户编号(https://developers.facebook.com/docs/reference/api/user)。但是一个访问令牌是必需的,我不知道如何(或者是否是完全可能)检索 ASP.NET 用于的访问令牌获取用户数据。
所以问题是:
-
如何指示 ASP.NET 身份引擎检索来自Facebook的其他信息,并将其包含在索赔中数据?
-
或者,如何检索生成的访问令牌,以便我可以自己问Facebook吗?
谢谢!
注意:对于身份验证系统,我的应用程序使用基于此SO答案中链接的示例项目的代码:https://stackoverflow.com/a/18423474/4574
在 Startup.ConfigureAuth (StartupAuth.cs) 中创建一个新的 Microsoft.Owin.Security.Facebook.AuthenticationOptions 对象,向其传递 FacebookAppId、FacebookAppSecret 和新的 AuthenticationProvider。 您将使用 lambda 表达式向 OnAuthenticated 方法传递一些代码,以将声明添加到包含从上下文中提取的值的标识。身份。 默认情况下,这将包括access_token。 您必须将电子邮件添加到范围。其他用户属性可从上下文中获得。用户(例如,请参阅底部的链接)。
StartUp.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中提取ClaimIdentity。 然后,我将其添加到使用应用程序 Cookie 创建的标识中。 我忽略了任何以"...schemas.xmlsoap.org/ws/2005/05/identity/claims",因为它似乎破坏了登录。
帐户控制器.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);
}
}
}
最后,我想显示任何不是来自地方当局的值。 我创建了一个显示在"/帐户/管理"页面上的部分视图_ExternalUserPropertiesListPartial。 我从 AuthenticationManager.User.Claims 获取以前存储的声明,然后将其传递给视图。
帐户控制器.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
任何反馈、更正或改进将不胜感激。
要从 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已发送,电子邮件已经检索并在此处添加为声明。你看不见吗?
它在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;
您需要创建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);
在上面的代码中,我通过context.AccessToken
访问access_token
并将其添加到当前登录用户的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");
以下是一些对您有所帮助的步骤。我正在写一篇博客文章,但这需要一段时间......- 在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",
});
使用访问令牌并调用 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/>"); }
我对所有答案的几美分...如果你仍然想自己问Facebook,看看已经存在的Facebook软件包是有意义的。它提供了一些已经实现的出色功能,因此您不必自己重新实现它......一些示例如何在MVC应用程序中使用它 ASP.NET 您可以在这里找到。