14

我正在尝试检索作为 OnAuthenticated 上下文返回的用户属性,并在此示例之后添加为声明: 如何使用 ASP.NET 身份 (OWIN) 访问 Facebook 私人信息?

我可以看到我期望的数据在登录时被返回,并被添加为 Starup.Auth.cs 中的声明。但是,当我在 Account Controller 中时,出现在 UserManager 或 UserStore 中的唯一声明是由 LOCAL AUTHORITY 发布的。找不到 Facebook(或其他外部提供商)的声明。添加到上下文中的声明在哪里结束?(我正在使用 VS2013 RTM。)

Azure 上的完整源代码和实时站点链接在这里:https ://github.com/johndpalm/IdentityUserPropertiesSample/tree/VS2013rtm

这是我在 Startup.Auth.cs 中的内容:

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) =>
            {
                const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
                foreach (var x in context.User)
                {
                    var claimType = string.Format("urn:facebook:{0}", x.Key);
                    string claimValue = x.Value.ToString();
                    if (!context.Identity.HasClaim(claimType, claimValue))
                        context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));

                }
                context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
                return Task.FromResult(0);
            }
    }

};

facebookOptions.Scope.Add("email");

app.UseFacebookAuthentication(facebookOptions);

捕获外部登录属性的另一种方法是为访问令牌添加一个声明并使用属性填充它:

const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
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) =>
        {
            var claim = new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook");
            foreach (var x in context.User)
            {
                string key = string.Format("urn:facebook:{0}", x.Key);
                string value = x.Value.ToString();
                claim.Properties.Add(key, value);
            }

            context.Identity.AddClaim(claim);

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

注意 - 此示例不起作用:尽管传递带有属性的单个声明会很好。外部 cookie 似乎注意到了声明属性。稍后从身份中检索属性时,这些属性为空。

4

3 回答 3

16

我能够使用 MVC 5 RTM 模板、OWIN 和 ASP.NET 标识位创建一个工作示例。您可以在此处找到完整的源代码和指向实时工作示例的链接: https ://github.com/johndpalm/IdentityUserPropertiesSample

这对我有用:

在 Startup.ConfigureAuth (StartupAuth.cs) 中创建一个新的(在此处插入提供程序名称)AuthenticationOptions 对象,将客户端 ID、客户端密码和新的 AuthenticationProvider 传递给它。您将使用 lambda 表达式向 OnAuthenticated 方法传递一些代码,以将声明添加到包含您从 context.Identity 中提取的值的标识中。

启动.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"));
                    foreach (var x in context.User)
                    {
                        var claimType = string.Format("urn:facebook:{0}", x.Key);
                        string claimValue = x.Value.ToString();
                        if (!context.Identity.HasClaim(claimType, claimValue))
                            context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));

                    }
                    return Task.FromResult(0);
                }
        }

    };
    app.UseFacebookAuthentication(facebookOptions);
}

注意:Facebook 身份验证提供程序使用此处使用的代码。如果您将相同的代码与 Microsoft 帐户提供程序(或我使用 MS 帐户代码作为模型创建的Foursquare 提供程序)一起使用,它将无法登录。如果您只选择 access_token 参数,它可以正常工作。似乎某些参数会破坏登录过程。(如果您对此进展感兴趣,已在 katanaproject.codeplex.com 上打开了一个问题。)如果我找到原因,我会更新。除了验证我是否可以获得 access_token 之外,我没有对 Twitter 或 Google 做太多事情。

var msaccountOptions = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions() 
{
    ClientId = ConfigurationManager.AppSettings.Get("MicrosoftClientId"),
    ClientSecret = ConfigurationManager.AppSettings.Get("MicrosoftClientSecret"),
    Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider()
    {
        OnAuthenticated = (context) =>
            {
                context.Identity.AddClaim(new System.Security.Claims.Claim("urn:microsoftaccount:access_token", context.AccessToken, XmlSchemaString, "Microsoft"));
                return Task.FromResult(0);
            }
    }                   
};

app.UseMicrosoftAccountAuthentication(msaccountOptions);

在 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-23T00:20:25.920 回答
1

所以这篇文章解释了这一切如何运作得很好:Decoupling owin external auth

但简短的回答是,当您从 facebook 获得身份验证时,这就是给您一个外部身份。然后,您需要获取该外部身份并“登录”本地应用程序身份,在该步骤中,您需要将所需的任何声明从外部身份添加到成为 User.Identity 的 ClaimsIdentity。

编辑:为了进一步澄清,您可以在 ExternalLoginCallback 内部进行:

    // GET: /Account/ExternalLoginCallback
    [AllowAnonymous]
    public async Task<ActionResult> ExternalLoginCallback(string returnUrl) {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
        if (loginInfo == null) {
            return RedirectToAction("Login");
        }

        // Sign in this external identity if its already linked
        var user = await UserManager.FindAsync(loginInfo.Login);
        if (user != null) {
            await SignInAsync(user, isPersistent: false);
            return RedirectToLocal(returnUrl);
        }

    private async Task SignInAsync(ApplicationUser user, bool isPersistent) {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
        var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
        AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
    }

因此,您需要将额外的数据传递给 SignIn,如下所示:

   ClaimsIdentity id = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

此 ClaimsIdentity 将包含您添加的声明,您需要将该声明添加到在 SignInAsync 方法中创建的标识中才能显示。

于 2013-10-19T01:11:13.297 回答
0

简而言之,使用 AddClaim 后所需的行如下:

取自上面的约翰斯回答。

ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
于 2013-10-26T06:34:30.210 回答