IdentityServer4自定义AuthenticationHandler无法找到用户的所有声明

时间:2018-01-11 13:02:25

标签: c# asp.net-identity identityserver4 claims-based-identity

我正在使用使用Asp.Net Identity和EntityFramework的IdentityServer4示例。

我正在尝试使用基于声明/角色的自定义策略创建组控件。

我的问题是,当我尝试在授权处理程序中获取用户声明时,我不会返回我想要的声明。

查看SSMS中的数据库我发现我创建的声明/角色位于名为“AspNetRoles”,“AspNetRoleClaims”,“AspNetUserClaims”的表中,我创建的用户位于“AspNetUsers”中,用户和角色的键在“AspNetUserRoles”中。 当我打电话让用户获得授权声明时,声明列表似乎来自“IdentityClaims”表。

似乎没有一种简单的方法来检查“AspNetClaims”中的声明,就像“IdentityClaims”中的声明一样,所以我假设我在某个地方犯了错误。

我已经找了一点解决方案并尝试了一些方法,但我找不到任何有效的方法。

以下是我认为与该问题最相关的代码以及对正在运行的代码的截图。

任何帮助都会非常感谢,提前谢谢。

代码

MvcClient.Startup

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthorization(options =>
    {
        options.AddPolicy("AdminRights", policy =>
        {                 
            policy.Requirements.Add(new AdminRequirement());
            policy.RequireAuthenticatedUser();
            policy.AddAuthenticationSchemes("Cookies");
        });
    });

    services.AddSingleton<IAuthorizationHandler, AdminRequirementHandler>();

    services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";

            options.Authority = "http://localhost:5000";
            options.RequireHttpsMetadata = false;

            options.ClientId = "mvc";
            options.ClientSecret = "secret";
            options.ResponseType = "code id_token token"; // NEW CHANGE (token)

            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Scope.Add("api1");
            options.Scope.Add("AdminPermission"); // NEW CHANGE
            options.Scope.Add("offline_access");
        });
}

MvcClient.Controllers.HomeController

[Authorize(Policy = "AdminRights")]
public IActionResult Administrator()
{
    return View();
}

IdentityServerWithAspIdAndEF.Startup(在配置中调用最后一次)

private async Task CreateSuperuser(IServiceProvider serviceProvider, ApplicationDbContext context)
{
    //adding custom roles
    var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
    var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();

    string[] roleNames = { "Administrator", "Internal", "Customer" };

    foreach (var roleName in roleNames)
    {
        //creating the roles and seeding them to the database
        var roleExist = await RoleManager.RoleExistsAsync(roleName);

        if (roleExist)
            await RoleManager.DeleteAsync( await RoleManager.FindByNameAsync(roleName) );

        var newRole = new IdentityRole(roleName);
        await RoleManager.CreateAsync(newRole);

        if(roleName == "Administrator")
            await RoleManager.AddClaimAsync(newRole, new Claim("AdminPermission", "Read"));
    }

    //creating a super user who could maintain the web app
    var poweruser = new ApplicationUser
    {
        UserName = Configuration.GetSection("UserSettings")["UserEmail"],
        Email = Configuration.GetSection("UserSettings")["UserEmail"]
    };

    string UserPassword = Configuration.GetSection("UserSettings")["UserPassword"];


    var _user = await UserManager.FindByEmailAsync(Configuration.GetSection("UserSettings")["UserEmail"]);

    if (_user != null)
        await UserManager.DeleteAsync( await UserManager.FindByEmailAsync(Configuration.GetSection("UserSettings")["UserEmail"]) );

    var createPowerUser = await UserManager.CreateAsync(poweruser, UserPassword);

    if (createPowerUser.Succeeded)
    {
        //here we tie the new user to the "Admin" role 
        await UserManager.AddToRoleAsync(poweruser, "Administrator");
        await UserManager.AddClaimAsync(poweruser, new Claim("AdminPermission", "Create"));
        await UserManager.AddClaimAsync(poweruser, new Claim("AdminPermission", "Update"));
        await UserManager.AddClaimAsync(poweruser, new Claim("AdminPermission", "Delete"));

    }
}

IdentityServerWithAspIdAndEF.Config

public class Config
{
    // scopes define the resources in your system
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResource()  // NEW CHANGE
            {
                Name = "AdminPermission",
                DisplayName = "Admin Permission",
                UserClaims =
                {
                    "AdminPermission",
                }
            }
        };
    }

    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("api1", "My API")
            {
                Scopes = // NEW CHANGE
                {
                    new Scope("AdminPermission", "Admin Permission")
                    {
                        UserClaims = { "AdminPermission" }
                    }
                }
            }
        };
    }

    // clients want to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
        // client credentials client
        return new List<Client>
        {
            // OpenID Connect hybrid flow and client credentials client (MVC)
            new Client
            {
                ClientId = "mvc",
                ClientName = "MVC Client",
                AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

                RequireConsent = false, // NEW CHANGE (false)

                ClientSecrets = 
                {
                    new Secret("secret".Sha256())
                },

                RedirectUris = { "http://localhost:5002/signin-oidc" },
                PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api1"
                    "AdminPermission", // NEW CHANGE
                },
                AllowOfflineAccess = true,
            },

            // Other Clients omitted as not used.
        };
    }
}

IdentityServerWithAspIdAndEF.Startup

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    // Add application services.
    services.AddTransient<IEmailSender, EmailSender>();

    services.AddMvc();

    string connectionString = Configuration.GetConnectionString("DefaultConnection");
    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

    // configure identity server with in-memory stores, keys, clients and scopes
    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddAspNetIdentity<ApplicationUser>()
        // this adds the config data from DB (clients, resources)
        .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(connectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));
        })
        // this adds the operational data from DB (codes, tokens, consents)
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(connectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));

            // this enables automatic token cleanup. this is optional.
            options.EnableTokenCleanup = true;
            options.TokenCleanupInterval = 30;
        });

    services.AddAuthentication()
        .AddGoogle("Google", options =>
        {
            options.ClientId = "434483408261-55tc8n0cs4ff1fe21ea8df2o443v2iuc.apps.googleusercontent.com";
                options.ClientSecret = "3gcoTrEDPPJ0ukn_aYYT6PWo";
        })
        .AddOpenIdConnect("oidc", "OpenID Connect", options =>
        {
            options.Authority = "https://demo.identityserver.io/";
            options.ClientId = "implicit";
            options.SaveTokens = true;

         // options.GetClaimsFromUserInfoEndpoint = true; // NEW CHANGE
         // options.ResponseType = "code id_token token";  // NEW CHANGE

            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name",
                RoleClaimType = "role"
            };
        });
}

MvcClient.Authorization

public class AdminRequirementHandler : AuthorizationHandler<AdminRequirement>
{

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement)
    {

        Console.WriteLine("User Identity: {0}", context.User.Identity);
        Console.WriteLine("Role is 'Administrator'? : {0}", context.User.IsInRole("Administrator"));
        Console.WriteLine("Identities of user:-");
        foreach (var v in context.User.Identities)
        {
            Console.WriteLine("\tName: {0},\tActor: {1},\tAuthType: {2},\tIsAuth: {3}", v.Name, v.Actor, v.AuthenticationType, v.IsAuthenticated);

            Console.WriteLine("\n\tClaims from Identity:-");
            foreach (var c in v.Claims)
                Console.WriteLine("\t\tType: {0},\tValue: {1},\tSubject: {2},\tIssuer: {3}", c.Type, c.Value, c.Subject, c.Issuer);
        }

        Console.WriteLine("Claims from other source:-");

        foreach(Claim c in context.User.Claims)
        {
            Console.WriteLine("\t\tType: {0},\tValue: {1},\tSubject: {2},\tIssuer: {3}", c.Type, c.Value, c.Subject, c.Issuer);
        }

        Console.WriteLine("\n *** Starting Authroization. ***\n");

        Claim
            role = context.User.FindFirst("role"),
            accessLevel = context.User.FindFirst("AdminPermission");


        if (role == null)
            Console.WriteLine("\tUser as no 'role' : '{0}'", role == null ? "null" : role.Value);
        else
            Console.WriteLine("\tUser has 'role' : '{0}'", role.Value);

        if (accessLevel == null)
            Console.WriteLine("\tUser has no claim 'AdminPermission' : '{0}'", accessLevel == null ? "null" : accessLevel.Value);
        else
            Console.WriteLine("\tUser has 'AdminPermission' : '{0}'", accessLevel.Value);

        if (role != null && accessLevel != null)
        {
            if (role.Value == "Administrator" && accessLevel.Value == "Read")
                context.Succeed(requirement);
        }
        else
            Console.WriteLine("\n *** Authorization Failue. ***\n");



        return Task.CompletedTask;
    }

}

数据

ApiClaims                       : 
ApiResources                    : api1
ApiScopeClaims                  : AdminPermission, ApiScopeId = 2
ApiScopes                       : api1, ApiResourceId = 1
                                : AdminPermission, ApiResourceId = 1
ApiSecrets                      :
AspNetRoleClaims                : AdminPermission, Read, RoleId = b2f03...
AspNetRoles                     : Administrator, b2f03... 
                                : Customer, 779f7...
                                : Internal, 10d5d...
AspNetUserClaims                : AdminPermission, Create, UserId = 8ee62...
                                : AdminPermission, Update, UserId = 8ee62...
                                : AdminPermission, Delete, UserId = 8ee62...
AspNetUserLogins                :
AspNetUserRoles                 : UserId = 8ee62..., RoleId = b2f03... 
AspNetUsers                     : superuser@mail.com, Id = 8ee62...
AspNetUserTokens                :
ClientClaims                    :
ClientCorsOrigins               :
ClientGrantTypes                : hybrid, ClientId = 1
                                : client_credentials, ClientId = 1
                                : client_credentials, ClientId = 2
                                : password, ClientId = 3
ClientIdPRestrictions           :
ClientPostLogoutRedirectUris    : http://localhost:5002/signout-callback-oidc, ClientId = 1
ClientProperties                :
ClientRedirectUris              : http://localhost:5002/signin-oidc, ClientId = 1
Clients                         : mvc, AllowAccessTokenViaBrowser = 1, AllowOfflineAccess = 1, RequireConsent = 0
                                : client ...
                                : ro.client ...
ClientScopes                    : openid, ClientId = 1
                                : profile, ClientId = 1
                                : AdminPermission, ClientId = 1
ClientSecrets                   : Type = SharedSecret
IdentityClaims                  :   Id  IdentityResourceId  Type
                                    1   1                   sub
                                    2   2                   name
                                    3   2                   family_name
                                    4   2                   given_name
                                    5   2                   middle_name
                                    6   2                   nickname
                                    7   2                   preferred_username
                                    8   2                   profile
                                    9   2                   picture
                                    10  2                   website
                                    11  2                   gender
                                    12  2                   birthdate
                                    13  2                   zoneinfo
                                    14  2                   locale
                                    15  2                   updated_at
                                    16  3                   AdminPermission
IdentityResources               : openid
                                : profile
                                : AdminPermission
PersistedGrants                 : [8x] Type = refresh_token

图片

Autos at the beginning of the AuthorizationHandler

Claims shown on the identity server

Claims shows on the MVC client

Log from when an authenticate user attempts to access

JWT received by MVC client

Database tables

1 个答案:

答案 0 :(得分:1)

<强> - 编辑 -

我已经分解了你的代码并解决了这个问题。

这是我的回购链接。

https://github.com/derekrivers/IdentityServer4

为了修复您的解决方案,我也更改了服务器身份验证响应类型: -

"code Id_token"

在config.cs的MVC Client设置中,我添加了以下属性: -

 AlwaysSendClientClaims = true, 
 AlwaysIncludeUserClaimsInIdToken = true 

我还从mvc客户端删除了 adminpermission 范围,因为它不是必需的。

我还稍微修改了 AdminRequirementHandler.cs ,但我会让你在我的回购中探索它。

基本上,我们确保用户声明属于身份令牌,然后通过这样做可以在 AdminRequirementHandler

中访问它们

希望这有帮助。