IdentityServer4自定义AuthenticationHandler找不到用户的所有声明 [英] IdentityServer4 custom AuthenticationHandler can't find all claims for a user
问题描述
我正在使用使用Asp.Net身份和EntityFramework的IdentityServer4示例.
I am using the IdentityServer4 sample that uses Asp.Net Identity and EntityFramework.
我正在尝试使用基于声明/角色的自定义策略来创建组控件.
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.
查看SSMS中的数据库,我发现我创建的声明/角色位于名为"AspNetRoles","AspNetRoleClaims","AspNetUserClaims"的表中,以及我在"AspNetUsers"中创建的用户以及用户和角色的键在"AspNetUserRoles"中. 当我打电话给用户寻求授权的索赔时,索赔清单似乎来自"IdentityClaims"表.
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". When I call to get the users claims for authorization the list of claims seems to come from the "IdentityClaims" table.
似乎没有一种简单的方法可以检查"AspNetClaims"中的声明,就像"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.
我一直在寻找解决方案,并尝试了一些尝试,但找不到任何可行的方法.
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.
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(在配置"中最后一次调用)
IdentityServerWithAspIdAndEF.Startup (Called last in Configure)
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
推荐答案
-编辑-
我已经分叉了您的代码,并解决了该问题.
I've Forked your code and solved the issue.
这是我的仓库的链接.
https://github.com/derekrivers/IdentityServer4
为了修复您的解决方案,我也更改了服务器身份验证响应类型:-
In order to fix your solution, i changed the server authentication response type too :-
"code Id_token"
在您的config.cs中的MVC客户端设置中,我添加了以下属性:-
In the MVC Client setup in your config.cs I added the following properties :-
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true
我还从mvc客户端中删除了 adminpermission 范围,因为它不是必需的.
I've also removed the adminpermission scope from the mvc client, as it isn't required.
我还稍微修改了 AdminRequirementHandler.cs ,但我将让您在我的存储库中进行探索.
I've also amended the AdminRequirementHandler.cs slightly, but i will let you explore that in my repo.
基本上,我们已经确保用户声明位于Identity令牌中,并且通过这样做,您可以在您的 AdminRequirementHandler
Basically, We have ensured that the user claims are in the Identity token, and by doing this they are then accessible within you AdminRequirementHandler
希望这会有所帮助.
这篇关于IdentityServer4自定义AuthenticationHandler找不到用户的所有声明的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!