Identity Server 4:向访问令牌添加声明

37

我正在使用Identity Server 4和Implicit Flow,并希望向访问令牌添加一些声明,新的声明或属性为“tenantId”和“langId”。

我已将langId添加为我的一个作用域,如下所示,然后通过身份验证服务器请求该作用域,但我也得到了tenantId。这是怎么回事?

这是作用域和客户端配置列表:

  public IEnumerable<Scope> GetScopes()
    {
        return new List<Scope>
        {
             // standard OpenID Connect scopes
            StandardScopes.OpenId,
            StandardScopes.ProfileAlwaysInclude,
            StandardScopes.EmailAlwaysInclude,

            new Scope
            {
                Name="langId",
                 Description = "Language",
                Type= ScopeType.Resource,
                Claims = new List<ScopeClaim>()
                {
                    new ScopeClaim("langId", true)
                }
            },
            new Scope
            {
                Name = "resourceAPIs",
                Description = "Resource APIs",
                Type= ScopeType.Resource
            },
            new Scope
            {
                Name = "security_api",
                Description = "Security APIs",
                Type= ScopeType.Resource
            },
        };
    }

客户:

  return new List<Client>
        {
            new Client
            {
                ClientName = "angular2client",
                ClientId = "angular2client",
                AccessTokenType = AccessTokenType.Jwt,
                AllowedGrantTypes = GrantTypes.Implicit,
                AllowAccessTokensViaBrowser = true,
                RedirectUris = new List<string>(redirectUris.Split(',')), 
                PostLogoutRedirectUris = new List<string>(postLogoutRedirectUris.Split(',')),
                AllowedCorsOrigins = new List<string>(allowedCorsOrigins.Split(',')),

                AllowedScopes = new List<string>
                {
                   "openid",
                   "resourceAPIs",
                   "security_api",         
                   "role",
                  "langId"
                }
            }
        };

我已经在ProfileService中添加了声明:

 public class ProfileService : IdentityServer4.Services.IProfileService
{
    private readonly SecurityCore.ServiceContracts.IUserService _userService;


    public ProfileService(SecurityCore.ServiceContracts.IUserService userService)
    {
        _userService = userService;
    }

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
       //hardcoded them just for testing purposes
        List<Claim> claims = new List<Claim>() { new Claim("langId", "en"), new Claim("tenantId", "123") };

        context.IssuedClaims = claims;


        return Task.FromResult(0);
    }

这就是我请求获取令牌的内容,问题是我只请求了 langId,但在访问令牌中却得到了 tenantIdlangId 两者。

http://localhost:44312/account/login?returnUrl=%2Fconnect%2Fauthorize%2Flogin%3Fresponse_type%3Did_token%2520token%26client_id%3Dangular2client%26redirect_uri%3Dhttp%253A%252F%252Flocalhost:5002%26scope%3DresourceAPIs%2520notifications_api%2520security_api%2520langId%2520navigation_api%2520openid%26nonce%3DN0.73617935552798141482424408851%26state%3D14824244088510.41368537145696305%26

解码后的访问令牌:

 {
  "nbf": 1483043742,
  "exp": 1483047342,
  "iss": "http://localhost:44312",
  "aud": "http://localhost:44312/resources",
  "client_id": "angular2client",
  "sub": "1",
  "auth_time": 1483043588,
  "idp": "local",
  "langId": "en",
  "tenantId": "123",
  "scope": [
    "resourceAPIs",     
    "security_api",
    "langId",
    "openid"
  ],
  "amr": [
    "pwd"
  ]
}

这是哪个版本的IdentityServer4? - Rafe
"IdentityServer4": "1.0.0-rc1-update2", “IdentityServer4”:“1.0.0-rc1-update2”, - Hussein Salman
你有没有更新这个到IS4 1.0 final或者1.2版本的机会? - Rafe
还没有,为什么?有什么问题吗? - Hussein Salman
1
请检查您的 ApiResource 的 UserClaims 来获取访问令牌。 - William Ardila
3个回答

58

这篇答案是针对Identityserver4在.Net Core 2上编写的,用于在.Net Core 3上使用,此答案可能会有所帮助,但您需要测试和更改一些内容。


我正在使用asp.net身份验证和实体框架与Identityserver4。
这是我的示例代码,运行良好且JWT包含所有角色和声明。
您可以在此处查看如何使用ASP.Net Core Identity实现Identityserver4: http://docs.identityserver.io/en/release/quickstarts/6_aspnet_identity.html https://github.com/IdentityServer/IdentityServer4.Samples/tree/dev/Quickstarts/6_AspNetIdentity 1- Identity Server的Startup.cs文件。

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

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

            services.AddMvc();

            services.AddTransient<IEmailSender, AuthMessageSender>();
            services.AddTransient<ISmsSender, AuthMessageSender>();

            //Add IdentityServer services
            //var certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "LocalhostCert.pfx"), "123456");
            services.AddIdentityServer()
                    .AddTemporarySigningCredential()
                    .AddInMemoryIdentityResources(Configs.IdentityServerConfig.GetIdentityResources())
                    .AddInMemoryApiResources(Configs.IdentityServerConfig.GetApiResources())
                    .AddInMemoryClients(Configs.IdentityServerConfig.GetClients())
                    .AddAspNetIdentity<ApplicationUser>()
                    .AddProfileService<Configs.IdentityProfileService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
                //app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseIdentity();

            // Adds IdentityServer
            app.UseIdentityServer();

            // Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Account}/{action=Login}/{id?}");
            });

        }

2- IdentityServerConfig.cs

using IdentityServer4;
    using IdentityServer4.Models;
    using System.Collections.Generic;

    namespace IdentityAuthority.Configs
    {

        public class IdentityServerConfig
        {

            // scopes define the resources in your system
            public static IEnumerable<IdentityResource> GetIdentityResources()
            {
                return new List<IdentityResource>
                {
                    new IdentityResources.OpenId(),
                    new IdentityResources.Profile()
                };
            }

            // scopes define the API resources
            public static IEnumerable<ApiResource> GetApiResources()
            {
                //Create api resource list
                List<ApiResource> apiResources = new List<ApiResource>();

                //Add Application Api API resource
                ApiResource applicationApi = new ApiResource("ApplicationApi", "Application Api");
                applicationApi.Description = "Application Api resource.";
                apiResources.Add(applicationApi);

                //Add Application Api API resource
                ApiResource definitionApi = new ApiResource("DefinitionApi", "Definition Api");
                definitionApi.Description = "Definition Api.";
                apiResources.Add(definitionApi);

                //Add FF API resource
                ApiResource ffApi = new ApiResource("FFAPI", "Fule .netfx API");
                ffApi.Description = "Test using .net 4.5 API application with IdentityServer3.AccessTokenValidation";
                apiResources.Add(ffApi);

                return apiResources;
            }

            // client want to access resources (aka scopes)
            public static IEnumerable<Client> GetClients()
            {
                //Create clients list like webui, console applications and...
                List<Client> clients = new List<Client>();

                //Add WebUI client
                Client webUi = new Client();
                webUi.ClientId = "U2EQlBHfcbuxUo";
                webUi.ClientSecrets.Add(new Secret("TbXuRy7SSF5wzH".Sha256()));
                webUi.ClientName = "WebUI";
                webUi.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;
                webUi.RequireConsent = false;
                webUi.AllowOfflineAccess = true;
                webUi.AlwaysSendClientClaims = true;
                webUi.AlwaysIncludeUserClaimsInIdToken = true;
                webUi.AllowedScopes.Add(IdentityServerConstants.StandardScopes.OpenId);
                webUi.AllowedScopes.Add(IdentityServerConstants.StandardScopes.Profile);
                webUi.AllowedScopes.Add("ApplicationApi");
                webUi.AllowedScopes.Add("DefinitionApi");
                webUi.AllowedScopes.Add("FFAPI");
                webUi.ClientUri = "http://localhost:5003";
                webUi.RedirectUris.Add("http://localhost:5003/signin-oidc");
                webUi.PostLogoutRedirectUris.Add("http://localhost:5003/signout-callback-oidc");
                clients.Add(webUi);

                //Add IIS test client
                Client iisClient = new Client();
                iisClient.ClientId = "b8zIsVfAl5hqZ3";
                iisClient.ClientSecrets.Add(new Secret("J0MchGJC8RzY7J".Sha256()));
                iisClient.ClientName = "IisClient";
                iisClient.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;
                iisClient.RequireConsent = false;
                iisClient.AllowOfflineAccess = true;
                iisClient.AlwaysSendClientClaims = true;
                iisClient.AlwaysIncludeUserClaimsInIdToken = true;
                iisClient.AllowedScopes.Add(IdentityServerConstants.StandardScopes.OpenId);
                iisClient.AllowedScopes.Add(IdentityServerConstants.StandardScopes.Profile);
                iisClient.AllowedScopes.Add("ApplicationApi");
                iisClient.AllowedScopes.Add("DefinitionApi");
                iisClient.AllowedScopes.Add("FFAPI");
                iisClient.ClientUri = "http://localhost:8080";
                iisClient.RedirectUris.Add("http://localhost:8080/signin-oidc");
                iisClient.PostLogoutRedirectUris.Add("http://localhost:8080/signout-callback-oidc");
                clients.Add(iisClient);

                return clients;
            }

        }
    }

3 - IdentityProfileService.cs

using IdentityServer4.Services;
using System;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityAuthority.Models;
using Microsoft.AspNetCore.Identity;
using IdentityServer4.Extensions;
using System.Linq;

namespace IdentityAuthority.Configs
{
    public class IdentityProfileService : IProfileService
    {

        private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
        private readonly UserManager<ApplicationUser> _userManager;

        public IdentityProfileService(IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory, UserManager<ApplicationUser> userManager)
        {
            _claimsFactory = claimsFactory;
            _userManager = userManager;
        }

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var sub = context.Subject.GetSubjectId();
            var user = await _userManager.FindByIdAsync(sub);
            if (user == null)
            {
                throw new ArgumentException("");
            }

            var principal = await _claimsFactory.CreateAsync(user);
            var claims = principal.Claims.ToList();

            //Add more claims like this
            //claims.Add(new System.Security.Claims.Claim("MyProfileID", user.Id));

            context.IssuedClaims = claims;
        }

        public async Task IsActiveAsync(IsActiveContext context)
        {
            var sub = context.Subject.GetSubjectId();
            var user = await _userManager.FindByIdAsync(sub);
            context.IsActive = user != null;
        }
    }

}

4 - 在我的客户端MVC Core项目中,我添加了3个NuGet包

.Microsoft.AspNetCore.Authentication.Cookies

.Microsoft.AspNetCore.Authentication.OpenIdConnect

.IdentityModel

5- 这是我客户端MVC Core项目的startup.cs文件

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                //app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            //Setup OpenId and Identity server
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationScheme = "Cookies",
                AutomaticAuthenticate = true
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
            {
                Authority = "http://localhost:5000",
                ClientId = "U2EQlBHfcbuxUo",
                ClientSecret = "TbXuRy7SSF5wzH",
                AuthenticationScheme = "oidc",
                SignInScheme = "Cookies",
                SaveTokens = true,
                RequireHttpsMetadata = false,
                GetClaimsFromUserInfoEndpoint = true,
                ResponseType = "code id_token",
                Scope = { "ApplicationApi", "DefinitionApi", "FFAPI", "openid", "profile", "offline_access" },
                TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "role"
                }
            });

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

6 - 在我的API中,我添加了这个NuGet包

.IdentityServer4.AccessTokenValidatio

我的startup.cs文件如下:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            //IdentityServer4.AccessTokenValidation
            app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
            {
                Authority = "http://localhost:5000",
                RequireHttpsMetadata = false,
                ApiName = "ApplicationApi"
            });

            app.UseMvc();
        }

现在我可以在客户端Web应用程序和API应用程序中使用[Authorize(Role="SuperAdmin, Admin")]。

User.IsInRole("Admin")

我也可以访问索赔信息

HttpContext.User.Claims 

var q = (from p in HttpContext.User.Claims where p.Type == "role" select p.Value).ToList();

var q2 = (from p in HttpContext.User.Claims where p.Type == "sub" select p.Value).First();

8
谢谢,伙计。我不怎么喜欢别人,但你的回答让我非常满意。身份认证疼痛得以避免。 - no1spirite
1
我正在IdentityProfileService中添加一个声明,并且它在令牌中,你知道如何仅在特定客户端中添加而不是所有客户端中添加吗?这是声明 //像这样添加更多声明 //claims.Add(new System.Security.Claims.Claim("Balance", user.Balance.ToString())); - William Ardila
嗨,William, 是的,如果您不想为客户添加声明或声明,可以检查"context.Client.ClientId"。我正在使用它来查找我的TenantId并在JWS中添加一个tenantId。 - Mohammad Karimi
1
@AlexandraDamaschin 不,我们有自己的私有 TFS。我很久以前就修改了这段代码,因为我们需要支持多租户,并且希望从数据库中读取 Identity-Server 设置,此外,我们还从 core 1.0 迁移到了 2.0.1。这段代码是我关于 Identity-Server 的第一个研究和开发。 - Mohammad Karimi
1
由于这行代码:var principal = await _claimsFactory.CreateAsync(user);,感谢它。 - ehsan
显示剩余2条评论

11
  • 登录过程中,服务器将颁发一个包含用户声明的认证cookie。
  • 接着,客户端在提供来自cookie的声明的同时,请求访问令牌,而配置文件服务将使用cookie声明生成访问令牌声明。
  • 然后,客户端会请求一个身份标识令牌,但这一次会使用来自访问令牌的声明。
  • 问题是,默认身份服务器的配置文件服务只是使用访问令牌中的声明来填充身份标识令牌的声明,而 ASP.Net Identity 的默认配置文件服务则会从数据库存储中查找所有用户声明。这是一个令人困惑的点。
  • 对于身份服务器实现来说,哪些声明最终会出现在访问令牌中?与API资源相关联的范围声明,而不是身份资源相关的范围声明,这些声明会出现在身份标识令牌中。
  • 总结
    • 没有使用 ASP.NET Identity:
      1. 登录 - 身份服务器颁发带有一些声明的cookie
      2. 访问令牌查询 - 身份服务器根据请求的API范围添加来自cookie的声明
      3. 身份标识令牌查询 - 身份服务器根据请求的身份范围添加来自访问令牌的声明
    • 使用 ASP.NET Identity:
      1. 登录 - 身份服务器发出带有某些声明的 cookie
      2. 访问令牌查询 - 身份服务器根据请求的 API 范围从 cookie 中添加声明
      3. ID 令牌查询 - 身份服务器根据请求的身份范围从访问令牌和用户存储中的声明添加声明

    8

    你是对的,我应该那样做,但这不是我的问题,因为如果我在上面的请求URL中没有请求 langId 范围。返回的访问令牌既不包含 langId 也不包含 tenantId(因此在这里进行过滤不是问题)。而这就是我需要理解的,为什么在请求问题中的 langId 时,tenantId 被包含在访问令牌中。 - Hussein Salman
    1
    这是因为其他资源范围没有任何声明分配给它们,所以当您删除langId范围时,此https://github.com/IdentityServer/IdentityServer4/blob/dev/src/IdentityServer4/Services/DefaultClaimsService.cs#L183条件不会触发。 - Aleksei Anufriev

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接