Как информация о пользователе Asp.Net Identity 2 сопоставляется с утверждениями профиля IdentityServer3 - PullRequest
0 голосов
/ 31 января 2019

У меня установлена ​​и работает Asp.Net Identity 2 с пользовательским хранилищем пользователей, поддерживаемым SQL Server через Dapper.На данный момент в моей разработке / тестировании я занимаюсь только локальными учетными записями (но буду добавлять внешних провайдеров входа).У меня есть пользовательский пользователь, который включает стандартные свойства, которые нужны Asp.Net Identity, и добавил несколько своих собственных (FirstName, LastName):

public class AppUser : IUser<Guid>
{
    public Guid Id { get; set; }
    public string UserName { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
    public string Email { get; set; }
    public bool EmailConfirmed { get; set; }
    public bool LockoutEnabled { get; set; }
    public DateTimeOffset LockoutEndDate { get; set; }
    public int AccessFailedCount { get; set; }

    // Custom User Properties
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

В моем веб-приложении MVC я настраиваю OIDC следующим образомИтак:

       app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            Authority = ConfigurationManager.AppSettings["OpenIdConnectAuthenticationOptions.Authority"],
            ClientId = "MVC.Web",
            Scope = "openid profile email",
            RedirectUri = ConfigurationManager.AppSettings["OpenIdConnectAuthenticationOptions.RedirectUri"],
            ResponseType = "id_token",
            SignInAsAuthenticationType = "Cookies"
        });

Поскольку я включил profile в качестве запрошенной области, я получаю:

preferred_username: testuser

И поскольку я включил email в качестве запрошенной области, я получаю:

email:          user@test.com
email_verified: true

Я явно не говорю своему AspNetIdentityUserService, как сопоставить свойство UserName в моем AppUser с утверждением preferred_username, и я не уверен, как это происходит. Поэтому я не понимаю, как связать свойство FirstName с заявкой given_name, чтобы оно было возвращено с id_token.

ЧтоЯ исследовал:

Так что, если вы посмотрите на образец IdentityServer3 AspNetIdentity здесь Я нашел это ClaimsIdentityFactory, которое выглядело так, как будто оно должно сработать:

    public override async Task<ClaimsIdentity> CreateAsync(UserManager<User, string> manager, User user, string authenticationType)
    {
        var ci = await base.CreateAsync(manager, user, authenticationType);
        if (!String.IsNullOrWhiteSpace(user.FirstName))
        {
            ci.AddClaim(new Claim("given_name", user.FirstName));
        }
        if (!String.IsNullOrWhiteSpace(user.LastName))
        {
            ci.AddClaim(new Claim("family_name", user.LastName));
        }
        return ci;
    }

Итак, я добавил это в свое приложение и подключил его в своем обычном UserManager.И я достигаю точки останова, когда создается экземпляр класса, но я никогда не достигаю точки останова для метода CreateAsync, и мои утверждения не возвращаются.

Я также видел это IdentityServer3 CustomПользовательский пример здесь , и я нашел этот GetProfileDataAsync метод, который выглядел так, как будто это было бы правильно (но мне кажется, что я копаю глубже, чем следовало бы для чего-то, казалось бы, такого простого / обычного):

    public override Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        // issue the claims for the user
        var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
        if (user != null)
        {
            context.IssuedClaims = user.Claims.Where(x => context.RequestedClaimTypes.Contains(x.Type));
        }

        return Task.FromResult(0);
    }

У меня была та же проблема, в том, что точка останова в этом методе никогда не была отключена.Я даже зашел так далеко, что посмотрел исходный код IdentityServer3 и увидел, что он вызывается только в том случае, если в области действия установлен флаг IncludeAllClaimsForUser.Но я здесь использую стандартную область действия profile, поэтому я начал сомневаться, нужно ли мне сделать собственное определение для области профиля, для которой установлен флаг IncludAllClaimsForUser, или есть способ добавить этот флаг встандартный охват.

И чтобы добавить ко всему этому ... Это нужно сделать только при использовании локальной учетной записи.Когда я внедряю внешних провайдеров входа в систему, я буду запрашивать там профиль и ожидать, что смогу получить имя и фамилию.Итак, мне интересно, что произойдет, когда у меня уже есть эти претензии (или как определить, нужно ли мне их извлекать из моего пользовательского магазина или нет).Похоже, мне нужно подключиться к чему-то, что запускается только при локальном входе в систему.

А потом я действительно начал задаваться вопросом, правильно ли я поступаю, так как вижу / нахожу так малоинформация об этом (я бы ожидал, что это будет довольно распространенный сценарий, который другие уже реализовали, и ожидал найти документы / образцы).Пытаюсь решить это за день сейчас.Надеюсь, у кого-то есть быстрый ответ / указатель!

Ответы [ 2 ]

0 голосов
/ 31 января 2019

(A) правильный ответ на этот вопрос - переопределить метод GetProfileDataAsync в классе AspNetIdentityUserService следующим образом:

public class AppUserService : AspNetIdentityUserService<AppUser, Guid>
{
    private AppUserManager _userManager;

    public AppUserService(AppUserManager userManager)
        : base(userManager)
    {
        _userManager = userManager;
    }

    public async override Task GetProfileDataAsync(ProfileDataRequestContext ctx)
    {
        var id = Guid.Empty;
        if (Guid.TryParse(ctx.Subject.GetSubjectId(), out id))
        {
            var user = await _userManager.FindByIdAsync(id);
            if (user != null)
            {
                var claims = new List<Claim>
                {
                    new Claim(Constants.ClaimTypes.PreferredUserName, user.UserName),
                    new Claim(Constants.ClaimTypes.Email, user.Email),
                    new Claim(Constants.ClaimTypes.GivenName, user.FirstName),
                    new Claim(Constants.ClaimTypes.FamilyName, user.LastName)
                };
                ctx.IssuedClaims = claims;
            }
        }
    }
}

Но, как я обнаружил, этого было недостаточно.Посмотрев исходный код для IdentityServer , вы обнаружите этот бит:

        if (scopes.IncludesAllClaimsForUserRule(ScopeType.Identity))
        {
            Logger.Info("All claims rule found - emitting all claims for user.");

            var context = new ProfileDataRequestContext(
                subject,
                client,
                Constants.ProfileDataCallers.ClaimsProviderIdentityToken);

            await _users.GetProfileDataAsync(context);

            var claims = FilterProtocolClaims(context.IssuedClaims);
            if (claims != null)
            {
                outputClaims.AddRange(claims);
            }

            return outputClaims;
        }

Обратите внимание, что GetProfileDataAsync не будет вызываться, если не установлен флаг, включающий все утверждения (не уверен, почему они решили сделать это таким образом, но, очевидно, должна быть веская причина!).Поэтому я подумал, что это означает, что мне нужно полностью переопределить область действия profile, но с дальнейшим поиском в источнике я обнаружил, что это не так.У StandardScopes есть метод, который создает области с установленным флагом всегда включения .Вместо того, чтобы устанавливать свои области видимости, сделайте следующее:

        factory.UseInMemoryScopes(StandardScopes.All);

Сделайте это:

        factory.UseInMemoryScopes(StandardScopes.AllAlwaysInclude);

ТОГДА ваш GetProfileDataAsync будет запущен, и вы получите все свои претензии!

Примечание: моя первая попытка использования ClaimsIdentityFactory не сработала, так как я не захожу в Asp.Net Identity, и есть смысл, что это никогда не будет вызвано, если я не будуделать.

Примечание: ответ @Rosdi Kasim, безусловно, действителен, если вы хотите добавить претензии (особенно специфичные для приложения) после того, как вы уже получили id_token от Identity Server.

0 голосов
/ 31 января 2019

Я использую OpenIdConnectAuthenticationNotifications, чтобы достичь этого, вы можете подключиться к базе данных ASP.NET Identity или сделать что-нибудь там, вот пример кода, который я использую для одного из моих проектов:

Это полныйисходный код из моего Startup.cs, но что вам действительно нужно, это просто раздел SecurityTokenValidated ...

using System.Configuration;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Helpers;
using IdentityServer3.Core;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;

namespace MyProject
{
    public partial class Startup
    {
        public static string AuthorizationServer => ConfigurationManager.AppSettings["security.idserver.Authority"];

        public void ConfigureOAuth(IAppBuilder app)
        {
            AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;

            var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
            jwtSecurityTokenHandler.InboundClaimTypeMap.Clear();

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Cookies"
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                SecurityTokenValidator = jwtSecurityTokenHandler,
                Authority = AuthorizationServer,
                ClientId = ConfigurationManager.AppSettings["security.idserver.clientId"],
                PostLogoutRedirectUri = ConfigurationManager.AppSettings["security.idserver.postLogoutRedirectUri"],
                RedirectUri = ConfigurationManager.AppSettings["security.idserver.redirectUri"],
                ResponseType = ConfigurationManager.AppSettings["security.idserver.responseType"],
                Scope = ConfigurationManager.AppSettings["security.idserver.scope"],
                SignInAsAuthenticationType = "Cookies",
#if DEBUG
                RequireHttpsMetadata = false,   //not recommended in production
#endif
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    RedirectToIdentityProvider = n =>
                    {
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                        {
                            var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                            if (idTokenHint != null)
                            {
                                n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                            }
                        }

                        return Task.FromResult(0);
                    },

                    SecurityTokenValidated = n =>
                    {
                        var id = n.AuthenticationTicket.Identity;

                        //// we want to keep first name, last name, subject and roles
                        //var givenName = id.FindFirst(Constants.ClaimTypes.GivenName);
                        //var familyName = id.FindFirst(Constants.ClaimTypes.FamilyName);
                        //var sub = id.FindFirst(Constants.ClaimTypes.Subject);
                        //var roles = id.FindAll(Constants.ClaimTypes.Role);

                        //// create new identity and set name and role claim type
                        var nid = new ClaimsIdentity(
                            id.AuthenticationType,
                            Constants.ClaimTypes.Name,
                            Constants.ClaimTypes.Role);

                        nid.AddClaims(id.Claims);
                        nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                        nid.AddClaim(new Claim("access_Token", n.ProtocolMessage.AccessToken));

                        ////nid.AddClaim(givenName);
                        ////nid.AddClaim(familyName);
                        ////nid.AddClaim(sub);
                        ////nid.AddClaims(roles);

                        ////// add some other app specific claim
                        // Connect to you ASP.NET database for example
                        ////nid.AddClaim(new Claim("app_specific", "some data"));

                        //// keep the id_token for logout
                        //nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));

                        n.AuthenticationTicket = new AuthenticationTicket(
                            nid,
                            n.AuthenticationTicket.Properties);

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

            //app.UseResourceAuthorization(new AuthorizationManager());
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...