IdentityServer4 return 使用 OpenId 的未知声明
IdentityServer4 return unknown claims using OpenId
我已经从 is4aspid
模板(使用 ASP.NET 用户管理身份的基本 IdentityServer)创建了一个 IdentityServer4 服务器,它带有 2 个示例用户。
之后,我按照 IdentityServer4 docs 的说明创建了一个 razor 页面客户端。
该应用程序运行正常,但应用程序从身份服务器获取未知声明,它们在此处 (type=value
):
s_hash=tFpbakJatWNQIjaChraJAw
sid=Sj6JGUgztjOIK1Cq8E-HoA
sub=a070c8cc-d962-440c-a796-e0c169e87578
auth_time=1586427090
idp=local
amr=pwd
我在 appsettings.json 上将服务器客户端定义为:
{
"ClientId": "mvc",
"Enabled": true,
"ClientName": "Mvc Client",
"AllowedGrantTypes": [ "client_credentials", "authorization_code" ],
"RequirePkce": true,
"ClientSecrets": [ { "Value": "hide_for_privacity" } ],
"RedirectUris": [ "https://localhost:5001/signin-oidc" ],
"FrontChannelLogoutUri": "http://localhost:5001/signout-oidc",
"PostLogoutRedirectUris": [ "http://localhost:5001/signout-callback-oidc" ],
"AllowOfflineAccess": true,
"AllowedScopes": [ "openid", "profile", "offline_access", "api1" ]
}
客户端的认证设置如下:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "http://localhost:5099";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "hide_for_privacity";
options.ResponseType = "code";
options.UsePkce = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.SaveTokens = true;
});
我已经使用 jwt.io 解码了返回的令牌,它是有效负载:
{
"nbf": 1586427096,
"exp": 1586427396,
"iss": "http://localhost:5099",
"aud": "mvc",
"nonce": "637220238868623664.MzdjNjI0NmMtNWNhMy00YTg4LWIzYTUtYjcxMTFmMTNlYjhiYWY0ZmM5NTQtNDY1Mi00ZWVhLTlkNjUtY2UzMzIwMjY5NjA4",
"iat": 1586427096,
"at_hash": "Z_Cwm-4UzmH8v8PyW2d0Rg",
"s_hash": "tFpbakJatWNQIjaChraJAw",
"sid": "Sj6JGUgztjOIK1Cq8E-HoA",
"sub": "a070c8cc-d962-440c-a796-e0c169e87578",
"auth_time": 1586427090,
"idp": "local",
"amr": ["pwd"]
}
为什么我没有收到服务器中定义的用户名和角色?
第一件事是确认您已向数据库添加角色并向用户添加角色。在 SeedData.cs
中,您可以像这样播种角色:
public static void SeedRoles(RoleManager<IdentityRole> roleManager)
{
if (!roleManager.RoleExistsAsync("NormalUser").Result)
{
IdentityRole role = new IdentityRole();
role.Name = "NormalUser";
IdentityResult roleResult = roleManager.
CreateAsync(role).Result;
}
if (!roleManager.RoleExistsAsync("Administrator").Result)
{
IdentityRole role = new IdentityRole();
role.Name = "Administrator";
IdentityResult roleResult = roleManager.
CreateAsync(role).Result;
}
}
并在 EnsureSeedData
函数中为用户添加角色:
...
SeedRoles(roleMgr);
var alice = userMgr.FindByNameAsync("alice").Result;
if (alice == null)
{
alice = new ApplicationUser
{
UserName = "alice"
};
var result = userMgr.CreateAsync(alice, "Pass123$").Result;
if (!result.Succeeded)
{
throw new Exception(result.Errors.First().Description);
}
userMgr.AddToRoleAsync(alice,"NormalUser").Wait();
...
}
之后,您可以向令牌添加自定义声明:
public class ProfileService : IProfileService
{
protected readonly UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
ApplicationUser user = await _userManager.GetUserAsync(context.Subject);
IList<string> roles = await _userManager.GetRolesAsync(user);
IList<Claim> roleClaims = new List<Claim>();
foreach (string role in roles)
{
roleClaims.Add(new Claim(JwtClaimTypes.Role, role));
}
//add user claims
roleClaims.Add(new Claim(JwtClaimTypes.Name, user.UserName));
context.IssuedClaims.AddRange(roleClaims);
}
public Task IsActiveAsync(IsActiveContext context)
{
return Task.CompletedTask;
}
}
并在Startup.cs注册:
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<ProfileService>(); <-- add this line
在 Config.cs
中,在您的客户端配置中,将 AlwaysIncludeUserClaimsInIdToken
设置为 true 以使声明在 ID 令牌中可用:
AlwaysIncludeUserClaimsInIdToken=true,
现在声明位于 ID Token 内,您还可以更改客户端应用程序中的 OIDC 配置以使用令牌中的 JwtClaimTypes.Role
类型设置角色声明:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
....
options.TokenValidationParameters.RoleClaimType = "role";
});
我已经从 is4aspid
模板(使用 ASP.NET 用户管理身份的基本 IdentityServer)创建了一个 IdentityServer4 服务器,它带有 2 个示例用户。
之后,我按照 IdentityServer4 docs 的说明创建了一个 razor 页面客户端。
该应用程序运行正常,但应用程序从身份服务器获取未知声明,它们在此处 (type=value
):
s_hash=tFpbakJatWNQIjaChraJAw
sid=Sj6JGUgztjOIK1Cq8E-HoA
sub=a070c8cc-d962-440c-a796-e0c169e87578
auth_time=1586427090
idp=local
amr=pwd
我在 appsettings.json 上将服务器客户端定义为:
{
"ClientId": "mvc",
"Enabled": true,
"ClientName": "Mvc Client",
"AllowedGrantTypes": [ "client_credentials", "authorization_code" ],
"RequirePkce": true,
"ClientSecrets": [ { "Value": "hide_for_privacity" } ],
"RedirectUris": [ "https://localhost:5001/signin-oidc" ],
"FrontChannelLogoutUri": "http://localhost:5001/signout-oidc",
"PostLogoutRedirectUris": [ "http://localhost:5001/signout-callback-oidc" ],
"AllowOfflineAccess": true,
"AllowedScopes": [ "openid", "profile", "offline_access", "api1" ]
}
客户端的认证设置如下:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "http://localhost:5099";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "hide_for_privacity";
options.ResponseType = "code";
options.UsePkce = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.SaveTokens = true;
});
我已经使用 jwt.io 解码了返回的令牌,它是有效负载:
{
"nbf": 1586427096,
"exp": 1586427396,
"iss": "http://localhost:5099",
"aud": "mvc",
"nonce": "637220238868623664.MzdjNjI0NmMtNWNhMy00YTg4LWIzYTUtYjcxMTFmMTNlYjhiYWY0ZmM5NTQtNDY1Mi00ZWVhLTlkNjUtY2UzMzIwMjY5NjA4",
"iat": 1586427096,
"at_hash": "Z_Cwm-4UzmH8v8PyW2d0Rg",
"s_hash": "tFpbakJatWNQIjaChraJAw",
"sid": "Sj6JGUgztjOIK1Cq8E-HoA",
"sub": "a070c8cc-d962-440c-a796-e0c169e87578",
"auth_time": 1586427090,
"idp": "local",
"amr": ["pwd"]
}
为什么我没有收到服务器中定义的用户名和角色?
第一件事是确认您已向数据库添加角色并向用户添加角色。在 SeedData.cs
中,您可以像这样播种角色:
public static void SeedRoles(RoleManager<IdentityRole> roleManager)
{
if (!roleManager.RoleExistsAsync("NormalUser").Result)
{
IdentityRole role = new IdentityRole();
role.Name = "NormalUser";
IdentityResult roleResult = roleManager.
CreateAsync(role).Result;
}
if (!roleManager.RoleExistsAsync("Administrator").Result)
{
IdentityRole role = new IdentityRole();
role.Name = "Administrator";
IdentityResult roleResult = roleManager.
CreateAsync(role).Result;
}
}
并在 EnsureSeedData
函数中为用户添加角色:
...
SeedRoles(roleMgr);
var alice = userMgr.FindByNameAsync("alice").Result;
if (alice == null)
{
alice = new ApplicationUser
{
UserName = "alice"
};
var result = userMgr.CreateAsync(alice, "Pass123$").Result;
if (!result.Succeeded)
{
throw new Exception(result.Errors.First().Description);
}
userMgr.AddToRoleAsync(alice,"NormalUser").Wait();
...
}
之后,您可以向令牌添加自定义声明:
public class ProfileService : IProfileService
{
protected readonly UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
ApplicationUser user = await _userManager.GetUserAsync(context.Subject);
IList<string> roles = await _userManager.GetRolesAsync(user);
IList<Claim> roleClaims = new List<Claim>();
foreach (string role in roles)
{
roleClaims.Add(new Claim(JwtClaimTypes.Role, role));
}
//add user claims
roleClaims.Add(new Claim(JwtClaimTypes.Name, user.UserName));
context.IssuedClaims.AddRange(roleClaims);
}
public Task IsActiveAsync(IsActiveContext context)
{
return Task.CompletedTask;
}
}
并在Startup.cs注册:
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<ProfileService>(); <-- add this line
在 Config.cs
中,在您的客户端配置中,将 AlwaysIncludeUserClaimsInIdToken
设置为 true 以使声明在 ID 令牌中可用:
AlwaysIncludeUserClaimsInIdToken=true,
现在声明位于 ID Token 内,您还可以更改客户端应用程序中的 OIDC 配置以使用令牌中的 JwtClaimTypes.Role
类型设置角色声明:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
....
options.TokenValidationParameters.RoleClaimType = "role";
});