外部登录需要 nameidentifier 声明才能成功。如何删除令牌中的重复键

nameidentifier claim is required for external login to succeed. How to remove duplicate keys in token

我终于成功地使用 IdentityServer4 设置了一个项目,以允许用户使用一个帐户登录多个应用程序。但是,我觉得不完全是应该的。

这是我的OAuthOptionsclass

public class CentralOptions : OAuthOptions
{
    public CentralOptions()
    {
        ClaimsIssuer = "https://localhost:44359";
        CallbackPath = new Microsoft.AspNetCore.Http.PathString("/signin-central");
        AuthorizationEndpoint = "https://localhost:44359/connect/authorize";
        TokenEndpoint = "https://localhost:44359/connect/token";
        UserInformationEndpoint = "https://localhost:44359/connect/userinfo";

        Scope.Add("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
        Scope.Add("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
        Scope.Add("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
        Scope.Add("openid");
        Scope.Add("profile");
        Scope.Add("email");
        Scope.Add("phone");
        Scope.Add("role");
        Scope.Add("weatherforecasts.read");
        Scope.Add("weatherforecasts.write");

        UsePkce = true;
        ClaimActions.MapJsonKey("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "sub");
        ClaimActions.MapJsonKey("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "name");
        ClaimActions.MapJsonKey("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "email");

        //ClaimActions.MapJsonKey("sub", "sub");
        //ClaimActions.MapJsonKey("name", "name");
        //ClaimActions.MapJsonKey("email", "email");
    }
}

如您所见,现在我必须复制 RequestedClaims,一次用于实际声明类型,一次用于某些简称。我一直在调整数据库的内容一段时间,但无法弄清楚我必须更改什么才能只拥有一次声明(我想 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress 不应该出现在不记名令牌中,并且email 应该。但是如果我更改数据库和应用程序中的声明,登录将失败,因为它依赖于 the ClaimTypes.NameIdentifier claim to be present in the bearer token

我登录后,IS 给了我一个访问令牌,例如:

eyJhbGciOiJSUzI1NiIsImtpZCI6IjVCOTBDN0JBNkExMjI2RjEyMEU0QzJGOEQzMjIwMzAxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MzcxNzkxNDAsImV4cCI6MTYzNzI2NTU0MCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTkiLCJhdWQiOiJ3ZWF0aGVyZm9yZWNhc3RzIiwiY2xpZW50X2lkIjoiU3NvQXBwbGljYXRpb25DbGllbnQiLCJjZW50cmFsLXRoZWNsaWVudCI6IlRoZSBTU08gY2xpZW50Iiwic3ViIjoiOTU5YzliZmEtZWQzMC00NjM4LTk5ODYtNjNjZjE1ODllZmY4IiwiYXV0aF90aW1lIjoxNjM3MTc5MTM3LCJpZHAiOiJsb2NhbCIsImVtYWlsIjoicGlldGVyamFuQGV4YW1wbGUuY29tIiwibmFtZSI6IlBpZXRlcmphbiIsImlkIjoiOTU5YzliZmEtZWQzMC00NjM4LTk5ODYtNjNjZjE1ODllZmY4IiwicGhvbmUiOiIrMzIxMjMvNDUuNjcuODkiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUGlldGVyamFuIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZW1haWxhZGRyZXNzIjoicGlldGVyamFuQGV4YW1wbGUuY29tIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbW9iaWxlcGhvbmUiOiIrMzIxMjMvNDUuNjcuODkiLCJqdGkiOiI3RjA0QTA5MDM3MUNEMjQ2MENCQzg3OUY3MDEwOTU1MyIsInNpZCI6IjA0NDYzRDlBRDNENDRCNUExQTNCQTRFOTczRUE5OTI4IiwiaWF0IjoxNjM3MTc5MTQwLCJzY29wZSI6WyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJwaG9uZSIsInJvbGUiLCJ3ZWF0aGVyZm9yZWNhc3RzLnJlYWQiLCJ3ZWF0aGVyZm9yZWNhc3RzLndyaXRlIl0sImFtciI6WyJwd2QiXX0.KBKLezXnUs6s-bU9hme7Ab7ADZN8DEewqfUncDwR0c2_LFqAnyCw3IZ85VJC4t-NN6xJYu8ROk-cX9PDKIQzEAOWGkOrQuqeaspKfIpl_rCq4qbP7x7uflToqPO245iU6xlzxVnGuaG1o_sSILNQA_YZJV8nsmXJkdB2QonuCZwvrBh5URFXV5cZpivlWznJls9eqfRM9MjlRpWe-NCI6I7FExfCaRgPZ4b1XwyrmmQWNlaKJOmIM3qag1pQshdXBSzg3w65htj89zOKKWSNl6Go6Q_0pZzbv0FLcMUMR_GTzuw56_CFobavD40T65wQQlXxf0cfkzbrdyAx7k8tyg

解码后看起来像这样

{
  "alg": "RS256",
  "kid": "5B90C7BA6A1226F120E4C2F8D3220301",
  "typ": "at+jwt"
}
{
  "nbf": 1637179140,
  "exp": 1637265540,
  "iss": "https://localhost:44359",
  "aud": "weatherforecasts",
  "client_id": "SsoApplicationClient",
  "central-theclient": "The SSO client",
  "sub": "959c9bfa-ed30-4638-9986-63cf1589eff8",
  "auth_time": 1637179137,
  "idp": "local",
  "email": "pieterjan@example.com",
  "name": "Pieterjan",
  "id": "959c9bfa-ed30-4638-9986-63cf1589eff8",
  "phone": "+32123/45.67.89",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Pieterjan",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "pieterjan@example.com",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone": "+32123/45.67.89",
  "jti": "7F04A090371CD2460CBC879F70109553",
  "sid": "04463D9AD3D44B5A1A3BA4E973EA9928",
  "iat": 1637179140,
  "scope": [
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
    "openid",
    "profile",
    "email",
    "phone",
    "role",
    "weatherforecasts.read",
    "weatherforecasts.write"
  ],
  "amr": [
    "pwd"
  ]
}

使用此令牌,您可以向

发送请求
https://localhost:44359/connect/userinfo

这给出了以下响应

{
    "email": "pieterjan@example.com",
    "name": "Pieterjan",
    "id": "959c9bfa-ed30-4638-9986-63cf1589eff8",
    "phone": "+32123/45.67.89",
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Pieterjan",
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "pieterjan@example.com",
    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone": "+32123/45.67.89",
    "sub": "959c9bfa-ed30-4638-9986-63cf1589eff8"
}

在我看来,您应该在响应中只有短名称限定符(emailnamesubphone),是吗正确的?但是如果我重新排列这个,/connect/userinfo 的响应将不包含 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name 声明,并且登录将在 following line 失败,它会在这个 UserInfo 的响应中查找这个确切的声明端点,因此将失败。

我想我必须调整 OAuthOptions.ClaimActions 当前阅读的内容:

ClaimActions.MapJsonKey("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "sub");
ClaimActions.MapJsonKey("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "name");
ClaimActions.MapJsonKey("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "email");

我已经在研究它们了,但最细微的修改会导致所有声明不再从 IdentityServer 返回。

dbo.Clients

dbo.ClientGrantType

= client.allowedGrantTypes

dbo.ClientSecret

= client.ClientSecrets

dbo.ClientScope

= client.AllowedScopes

所以在这里你看到我现在有可能不应该在数据库中的范围,因为它们实际上是声明类型,但如果我删除它们,NameIdentifier 声明类型将不存在在身份中。

dbo.ClientRedirectUri

= client.RedirectUris

dbo.ClientClaim

= client.Claims

dbo.IdentityResources

dbo.IdentityResourceClaim

= identityResource.UserClaims

其中标记为:

List of associated user claims that should be included when this resource is requested.

很明显,我必须引入双线才能让外部登录开始工作。

dbo.AspNetUsers

dbo.AspNetUserClaims

= user.Claims

如何正确设置我的代码和数据库,以便我的应用程序不再需要那些重复的声明,work/my 外部登录才能成功?

此外,声明应该在数据库级别持久化,还是 generated during login

提前致谢。

Git repository

我只会在 IdentityServer 中使用较短的声明名称,并在客户端中进行必要的声明转换或映射。

我会考虑在客户端或 API 使用 MapUniqueJsonKey 进行转换:

options.ClaimActions.MapUniqueJsonKey("website", "website");
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
options.ClaimActions.MapUniqueJsonKey("birthdate", "birthdate");

我认为了解 ClaimsPrincipal 用户对象在身份验证后(但在授权前)包含的内容很重要。

如果需要更高级的转换,请查看使用 IClaimsTransformation 接口。

更多信息: