身份服务器已实现且运行良好。 Google 登录正在运行,并且正在返回包括电子邮件在内的多项声明。
Facebook 登录正常,我的应用程序已上线,并在新用户登录时请求电子邮件权限。
问题是我无法从 oauth 端点取回电子邮件,而且我似乎找不到 access_token 来手动请求用户信息。我所拥有的只是从 facebook 登录端点返回的“代码”。
这是 IdentityServer 设置。
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"]
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
当然,我已经自定义了 AuthenticateLocalAsync 方法,但我收到的声明仅包含名称。没有电子邮件 claim 。
深入研究身份服务器的源代码,我意识到发生了一些声明事情来转换 facebook 声明,因此我扩展了该类来调试它,看看它是否剥离了任何声明,但事实并非如此。
我还用 fiddler 观看了 http 调用,我只看到以下内容(抱歉,代码格式在 url 上效果不佳。我尝试将查询字符串参数格式化为自己的行,但没有成功)
(facebook.com)
/dialog/oauth ?response_type=代码 &client_id=xxx &redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook &范围=电子邮件 &state=xxx
(facebook.com)
/登录.php ?skip_api_login=1 &api_key=xxx &signed_next=1 &next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope% 3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx&cancel_url=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions% 2Berror%26error_reason%3Duser_denied%26state%3Dxxx%23_%3D_ &显示=页面 &locale=en_US &logger_id=xxx
(facebook.com)
POST/cookie/consent/?pv=1&dpr=1 HTTP/1.1
(facebook.com)
/登录.php ?login_attempt=1 &next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope% 3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx &lwv=100
(facebook.com)
/v2.7/dialog/oauth ?redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook &状态=xxx &范围=电子邮件 &response_type=代码 &client_id=xxx &ret=登录 &logger_id=xxx &hash=xxx
(身份服务器)
/id/登录-facebook ?代码=xxx &state=xxx
我在最后一次调用中看到了 code 参数,并认为也许我可以使用那里的代码从 facebook API https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow 获取 access_token
但是,当我尝试时,我从 API 收到一条消息,告诉我该代码已被使用。
我还尝试将 UserInformationEndpoint 更改为 FacebookAuthenticationOptions,通过将 ?fields=email 附加到默认端点位置的末尾来强制它请求电子邮件,但这会导致身份服务器吐出错误“There was an登录外部提供商时出错。错误消息为:access_denied”。
如果我可以更改中间件以使用response_type=id_token发送请求,我也许能够解决这一切,但我不知道如何做到这一点,也不知道如何在第一次返回时提取该访问 token 能够使用 Facebook C# sdk 的地方。
所以我想任何帮助或指导都会很棒。我花了无数的时间研究并试图解决这个问题。我需要做的就是通过 IdentityServer3 获取登录用户的电子邮件地址。听起来并不难,但我却卡住了。
最佳答案
我终于明白了这一点。答案与 Mitra 的评论有关,尽管这两个答案似乎都不符合要求,所以我在这里放了另一个答案。首先,您需要从 Facebook 的身份验证端点请求 access_token,而不是代码(授权代码)。为此,请像这样设置
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"],
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);
}
}
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
然后,您需要在登录后捕获响应。我正在使用以下 file来自IdentityServer3 Samples Repository ,它覆盖(读取,提供功能)从外部站点登录用户所需的方法。从这个回复中,我使用 C# Facebook SDK使用ExternalAuthenticationContext中新返回的access_token声明来请求我需要的字段并将它们添加到声明列表中。然后我可以使用该信息来创建/登录用户。
public override async Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
{
var externalUser = ctx.ExternalIdentity;
var claimsList = ctx.ExternalIdentity.Claims.ToList();
if (externalUser.Provider == "Facebook")
{
var extraClaims = GetAdditionalFacebookClaims(externalUser.Claims.First(claim => claim.Type == "urn:facebook:access_token"));
claimsList.Add(new Claim("email", extraClaims.First(k => k.Key == "email").Value.ToString()));
claimsList.Add(new Claim("given_name", extraClaims.First(k => k.Key == "first_name").Value.ToString()));
claimsList.Add(new Claim("family_name", extraClaims.First(k => k.Key == "last_name").Value.ToString()));
}
if (externalUser == null)
{
throw new ArgumentNullException("externalUser");
}
var user = await userManager.FindAsync(new Microsoft.AspNet.Identity.UserLoginInfo(externalUser.Provider, externalUser.ProviderId));
if (user == null)
{
ctx.AuthenticateResult = await ProcessNewExternalAccountAsync(externalUser.Provider, externalUser.ProviderId, claimsList);
}
else
{
ctx.AuthenticateResult = await ProcessExistingExternalAccountAsync(user.Id, externalUser.Provider, externalUser.ProviderId, claimsList);
}
}
就是这样!如果您对简化此过程有任何建议,请告诉我。我打算修改此代码以执行从 FacebookAuthenticationOptions 对 API 的调用,但 Events 属性显然不再存在。
编辑:GetAdditionalFacebookClaims 方法只是一种根据已提取的访问 token 创建新 FacebookClient 的方法,并在 Facebook API 中查询您需要的其他用户声明。例如,我的方法如下所示:
protected static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new {fields = new[] {"email", "first_name", "last_name"}}) as JsonObject;
}
关于facebook-graph-api - Identity Server 3 Facebook 登录 获取电子邮件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39172101/