简体   繁体   English

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

[英]IdentityServer4 custom AuthenticationHandler can't find all claims for a user

I am using the IdentityServer4 sample that uses Asp.Net Identity and EntityFramework. 我正在使用使用Asp.Net身份和EntityFramework的IdentityServer4示例。

I am trying to create group controls using custom policies based on claims/roles. 我正在尝试使用基于声明/角色的自定义策略来创建组控件。

My problem is that when I try and get the users claims in the authorization handler the claims I am looking for are not returned. 我的问题是,当我尝试在授权处理程序中获取用户声明时,不会返回我要查找的声明。

Looking at the database in SSMS I find the claims/roles I created are in tables called "AspNetRoles", "AspNetRoleClaims", "AspNetUserClaims" along with the user I created being in "AspNetUsers" and keys for the user and role being in "AspNetUserRoles". 在SSMS中查看数据库,我发现我创建的声明/角色位于名为“ AspNetRoles”,“ AspNetRoleClaims”,“ AspNetUserClaims”的表中,以及我创建的用户位于“ AspNetUsers”中,而用户和角色的密钥位于“ AspNetUserRoles”。 When I call to get the users claims for authorization the list of claims seems to come from the "IdentityClaims" table. 当我打电话要求用户授权时,索赔清单似乎来自“ IdentityClaims”表。

There doesn't seem to be a simple way to check for claims in "AspNetClaims" like there is for claims in "IdentityClaims" so I assume I've made an error somewhere. 似乎没有一种简单的方法来检查“ AspNetClaims”中的声明,就像“ IdentityClaims”中的声明一样,因此我认为我在某个地方出错了。

I've looked around a fair bit for a solution and tried a fair few things but I can't find anything that works. 我到处寻找解决方案,并尝试了一些尝试,但找不到任何可行的方法。

Below is the code i thought would me most relevant to the question along with some screenshots taken of the running code. 以下是我认为与该问题最相关的代码,以及一些正在运行的代码的屏幕截图。

Any help would be much appreciated, thanks in advance. 任何帮助将不胜感激,在此先感谢。


Code

MvcClient.Startup 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 MvcClient.Controllers.HomeController

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

IdentityServerWithAspIdAndEF.Startup (Called last in Configure) 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 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 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 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;
    }

}

Data 数据

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

Images 图片

Autos at the beginning of the AuthorizationHandler 在AuthorizationHandler的开头自动运行

Claims shown on the identity server 身份服务器上显示的声明

Claims shows on the MVC client 索赔显示在MVC客户端上

Log from when an authenticate user attempts to access 通过身份验证的用户尝试访问时的日志

JWT received by MVC client MVC客户端收到JWT

Database tables 数据库表

--EDIT-- - 编辑 -

I've Forked your code and solved the issue. 我已经分叉了您的代码并解决了问题。

Here is a link to my repo. 这是我的仓库的链接。

https://github.com/derekrivers/IdentityServer4 https://github.com/derekrivers/IdentityServer4

In order to fix your solution, i changed the server authentication response type too :- 为了解决您的解决方案,我也更改了服务器身份验证响应类型:-

"code Id_token"

In the MVC Client setup in your config.cs I added the following properties :- 在config.cs的MVC客户端设置中,我添加了以下属性:-

 AlwaysSendClientClaims = true, 
 AlwaysIncludeUserClaimsInIdToken = true 

I've also removed the adminpermission scope from the mvc client, as it isn't required. 我还从mvc客户端中删除了管理员权限范围,因为它不是必需的。

I've also amended the AdminRequirementHandler.cs slightly, but i will let you explore that in my repo. 我还稍微修改了AdminRequirementHandler.cs ,但我将让您在我的存储库中进行探索。

Basically, We have ensured that the user claims are in the Identity token, and by doing this they are then accessible within you AdminRequirementHandler 基本上,我们确保用户声明位于Identity令牌中,并且通过这样做,您可以在您的AdminRequirementHandler中访问它们。

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM