zoukankan      html  css  js  c++  java
  • 在 IdentityServer4 中创建客户端

    创建客户端

    在创建了 IdentityServer4 服务器之后,我们可以准备从获取一个访问令牌开始。

    1. 客户端凭证式验证流

    在 OpenID Connect 中,最为简单的验证方式为客户端凭借方式了。我们从这种方式开始。OpenID Connect 是 OAuth 的扩展,我们找一段阮一峰的博客来进行说明。

    第四种方式:凭证式

    最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。

    第一步,A 应用在命令行向 B 发出请求。

    https://oauth.b.com/token?
      grant_type=client_credentials&
      client_id=CLIENT_ID&
      client_secret=CLIENT_SECRET
    

    上面 URL 中,grant_type参数等于client_credentials表示采用凭证式,client_idclient_secret用来让 B 确认 A 的身份。

    第二步,B 网站验证通过以后,直接返回令牌。

    这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。

    原文地址:http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

    2. 服务器端实现

    实际的服务端点在上一篇中,通过访问端点 http://localhost:5000/.well-known/openid-configuration 就可以得到,从响应的结果中可以找到如下的一行:

     "token_endpoint": "http://localhost:5000/connect/token",
    

    而客户端的标示和密钥则需要我们在服务器端提供。

    将上一个项目中的 Config.cs 文件替换为如下内容,代码中硬编码了客户端的标识为 client ,密钥为 secret

    // Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
    // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
    
    
    using IdentityServer4.Models;
    using System.Collections.Generic;
    
    namespace IdentityServer
    {
        public static class Config
        {
            public static IEnumerable<IdentityResource> Ids =>
                new IdentityResource[]
                { 
                    new IdentityResources.OpenId()
                };
    
            // scopes define the API resources in your system
            public static IEnumerable<ApiResource> Apis =>
                new List<ApiResource>
                {
                    new ApiResource("api1", "My API")
                };
            
    
            // clients want to access resources (aka scopes)
            public static IEnumerable<Client> Clients =>
                new List<Client>
                {
                    new Client
                    {
                        ClientId = "client",
                        AllowedGrantTypes = GrantTypes.ClientCredentials,
    
                        ClientSecrets =
                        {
                            new Secret("secret".Sha256())
                        },
                        AllowedScopes = { "api1" }
                    }
                };
        }
    }
    

    在这里通过 Apis 这个委托提供了 API 资源,通过 Clients 这个委托提供了客户端的凭据。

    在启动应用之后,我们可以访问服务器来获取令牌。

    3. 获取访问令牌

    这里,我们使用 Postman 来完成。

    将请求的发送方式修改为 Post

    地址设置为:http://localhost:5000/connect/token

    请求的 Body ,在 Body 的设置中,首先选中格式:x-www-form-urlencoded,提供如下三个参数:

    KEY VALUE
    grant_type client_credentials
    client_id Client
    client_secret Secret

    点击 Send 按钮,发送请求之后,就应该可以看到如下的响应内容:

    {
        "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IklWR2VMQ3h5NFJjcWJnbmUxb1JVN3ciLCJ0eXAiOiJhdCtqd3QifQ.eyJuYmYiOjE1ODU4MTQwNzMsImV4cCI6MTU4NTgxNzY3MywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiYXBpMSIsImNsaWVudF9pZCI6ImNsaWVudCIsInNjb3BlIjpbImFwaTEiXX0.R5RNGRM6bVvdNIgdXnD-QK5HK-kHA5hcZ-ltn0K3kLZp9R3BGQeg5qfnQXT4sU2CqPGYIatwbZY3bysQ9krkq5BpWzSzwY7EYPybsP3gty0BUK2QXnEwxsT1boN_cM2Hw9ua4nal3IHB4XJJkMj7jo33S8NtQQyJr26_G1WqlOgvlVfUiPYQWiY9OHPgTAIqrU_4aogoxiC84lHWC5Pf6oX6jxLoAWzKkhl-NdH33gW169xdtkPXp51XpbXhxNujBo7LAVOI-_5ztouuYLShOf5bOt1bunHfeNCv1DPl2rBsfFITjkoltQXVrTSZGLEQgNH_ryBqdoTyM-jWP1HN4g",
        "expires_in": 3600,
        "token_type": "Bearer",
        "scope": "api1"
    }
    

    4. 实现自定义的存储

    这个 Config 中提供的静态属性只是为了方便进行简单的测试,在 IdentityServer4 内部,定义了验证客户端的接口 IClientStore,如下所示:

    // Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
    // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
    
    
    using IdentityServer4.Models;
    using System.Threading.Tasks;
    
    namespace IdentityServer4.Stores
    {
        /// <summary>
        /// Retrieval of client configuration
        /// </summary>
        public interface IClientStore
        {
            /// <summary>
            /// Finds a client by id
            /// </summary>
            /// <param name="clientId">The client id</param>
            /// <returns>The client</returns>
            Task<Client> FindClientByIdAsync(string clientId);
        }
    }
    

    源码地址:https://github.com/IdentityServer/IdentityServer4/blob/master/src/Storage/src/Stores/IClientStore.cs

    在 IdentityServer4 的内部,也实现了一个基于内存中集合实现的内存中客户端存储 InMemoryClientStore,代码实现如下:

    // Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
    // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
    
    
    using IdentityServer4.Extensions;
    using IdentityServer4.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace IdentityServer4.Stores
    {
        /// <summary>
        /// In-memory client store
        /// </summary>
        public class InMemoryClientStore : IClientStore
        {
            private readonly IEnumerable<Client> _clients;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="InMemoryClientStore"/> class.
            /// </summary>
            /// <param name="clients">The clients.</param>
            public InMemoryClientStore(IEnumerable<Client> clients)
            {
                if (clients.HasDuplicates(m => m.ClientId))
                {
                    throw new ArgumentException("Clients must not contain duplicate ids");
                }
                _clients = clients;
            }
    
            /// <summary>
            /// Finds a client by id
            /// </summary>
            /// <param name="clientId">The client id</param>
            /// <returns>
            /// The client
            /// </returns>
            public Task<Client> FindClientByIdAsync(string clientId)
            {
                var query =
                    from client in _clients
                    where client.ClientId == clientId
                    select client;
                
                return Task.FromResult(query.SingleOrDefault());
            }
        }
    }
    

    源码地址:https://github.com/IdentityServer/IdentityServer4/blob/master/src/IdentityServer4/src/Stores/InMemory/InMemoryClientStore.cs

    你看,我们只需要传一个 Client 的可迭代对象就可以构造这样一个 InMemoryClientStore 对象实例。实际上,我们调用的 AddInMemoryClients 这个扩展方法确实就是这么做的:

    /// <summary>
    /// Adds the in memory clients.
    /// </summary>
    /// <param name="builder">The builder.</param>
    /// <param name="clients">The clients.</param>
    /// <returns></returns>
    public static IIdentityServerBuilder AddInMemoryClients(this IIdentityServerBuilder builder, IEnumerable<Client> clients)
    {
      builder.Services.AddSingleton(clients);
    
      builder.AddClientStore<InMemoryClientStore>();
    
      var existingCors = builder.Services.Where(x => x.ServiceType == typeof(ICorsPolicyService)).LastOrDefault();
      if (existingCors != null && 
          existingCors.ImplementationType == typeof(DefaultCorsPolicyService) && 
          existingCors.Lifetime == ServiceLifetime.Transient)
      {
        // if our default is registered, then overwrite with the InMemoryCorsPolicyService
        // otherwise don't overwrite with the InMemoryCorsPolicyService, which uses the custom one registered by the host
        builder.Services.AddTransient<ICorsPolicyService, InMemoryCorsPolicyService>();
      }
    
      return builder;
    }
    

    源码地址:https://github.com/IdentityServer/IdentityServer4/blob/master/src/IdentityServer4/src/Configuration/DependencyInjection/BuilderExtensions/InMemory.cs

    这里使用了依赖注入完成 InMemoryClientStore 的构造注入。

    好了,我们自己实现一个自定义的客户端凭据存储。比如 CustomClientStore。

    在构造函数中,我们构建了内部存储客户端凭据的集合,由于实现了 IClientStore ,在实现的方法中,提供了检索客户端端的实现,其实这个方法完全从 InMemoryClientStore 复制过来的。

    using IdentityServer4.Models;
    using IdentityServer4.Stores;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace IdentityServer
    {
        public class CustomClientStore : IClientStore
        {
            private readonly IEnumerable<Client> _clients;
            public CustomClientStore()
            {
                _clients = new List<Client>
                {
                    new Client
                    {
                        ClientId = "client",
                        AllowedGrantTypes = GrantTypes.ClientCredentials,
    
                        ClientSecrets =
                        {
                            new Secret("secret".Sha256())
                        },
                        AllowedScopes = { "api1" }
                    }
                };
            }
    
            public Task<Client> FindClientByIdAsync(string clientId)
            {
                var query =
                    from client in _clients
                    where client.ClientId == clientId
                    select client;
                
                return Task.FromResult(query.SingleOrDefault());
            }
        }
    }
    

    在 .NET Core 中,服务是通过依赖注入的方式被使用的,所以,我们需要注册这个服务。回到 Startup.cs 这个文件,将 ConfigureServices() 方法替换为如下内容。

    public void ConfigureServices(IServiceCollection services)
    {
      // uncomment, if you want to add an MVC-based UI
      //services.AddControllersWithViews();
    
      services.AddSingleton<IClientStore, CustomClientStore>();
    
      var builder = services.AddIdentityServer()
        .AddInMemoryIdentityResources(Config.Ids)
        .AddInMemoryApiResources(Config.Apis);
    
      // not recommended for production - you need to store your key material somewhere secure
      builder.AddDeveloperSigningCredential();
    }
    

    主要做了两件事:

    1. 删除了原来的 .AddInMemoryClients(Config.Clients);
    2. 添加了 services.AddSingleton<IClientStore, CustomClientStore>(); 来注册服务实现

    重新运行程序,并使用 Postman 访问,可以重新得到一个新的访问令牌。

    控制台输出如下所示:

    PS C:	empis4IdentityServer> dotnet run
    [02:06:36 Information]
    Starting host...
    
    [02:06:37 Information] IdentityServer4.Startup
    Starting IdentityServer4 version 3.1.0.0
    
    [02:06:37 Information] IdentityServer4.Startup
    You are using the in-memory version of the persisted grant store. This will store consent decisions, authorization codes
    , refresh and reference tokens in memory only. If you are using any of those features in production, you want to switch
    to a different store implementation.
    
    [02:06:37 Information] IdentityServer4.Startup
    Using the default authentication scheme idsrv for IdentityServer
    
    [02:06:37 Debug] IdentityServer4.Startup
    Using idsrv as default ASP.NET Core scheme for authentication
    
    [02:06:37 Debug] IdentityServer4.Startup
    Using idsrv as default ASP.NET Core scheme for sign-in
    
    [02:06:37 Debug] IdentityServer4.Startup
    Using idsrv as default ASP.NET Core scheme for sign-out
    
    [02:06:37 Debug] IdentityServer4.Startup
    Using idsrv as default ASP.NET Core scheme for challenge
    
    [02:06:37 Debug] IdentityServer4.Startup
    Using idsrv as default ASP.NET Core scheme for forbid
    
    [02:06:59 Debug] IdentityServer4.Startup
    Login Url: /Account/Login
    
    [02:06:59 Debug] IdentityServer4.Startup
    Login Return Url Parameter: ReturnUrl
    
    [02:06:59 Debug] IdentityServer4.Startup
    Logout Url: /Account/Logout
    
    [02:06:59 Debug] IdentityServer4.Startup
    ConsentUrl Url: /consent
    
    [02:06:59 Debug] IdentityServer4.Startup
    Consent Return Url Parameter: returnUrl
    
    [02:06:59 Debug] IdentityServer4.Startup
    Error Url: /home/error
    
    [02:06:59 Debug] IdentityServer4.Startup
    Error Id Parameter: errorId
    
    [02:06:59 Debug] IdentityServer4.Hosting.EndpointRouter
    Request path /connect/token matched to endpoint type Token
    
    [02:06:59 Debug] IdentityServer4.Hosting.EndpointRouter
    Endpoint enabled: Token, successfully created handler: IdentityServer4.Endpoints.TokenEndpoint
    
    [02:06:59 Information] IdentityServer4.Hosting.IdentityServerMiddleware
    Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token
    
    [02:06:59 Debug] IdentityServer4.Endpoints.TokenEndpoint
    Start token request.
    
    [02:06:59 Debug] IdentityServer4.Validation.ClientSecretValidator
    Start client validation
    
    [02:06:59 Debug] IdentityServer4.Validation.BasicAuthenticationSecretParser
    Start parsing Basic Authentication secret
    
    [02:06:59 Debug] IdentityServer4.Validation.PostBodySecretParser
    Start parsing for secret in post body
    
    [02:06:59 Debug] IdentityServer4.Validation.SecretParser
    Parser found secret: PostBodySecretParser
    
    [02:06:59 Debug] IdentityServer4.Validation.SecretParser
    Secret id found: client
    
    [02:06:59 Debug] IdentityServer4.Validation.SecretValidator
    Secret validator success: HashedSharedSecretValidator
    
    [02:06:59 Debug] IdentityServer4.Validation.ClientSecretValidator
    Client validation success
    
    [02:06:59 Debug] IdentityServer4.Validation.TokenRequestValidator
    Start token request validation
    
    [02:06:59 Debug] IdentityServer4.Validation.TokenRequestValidator
    Start client credentials token request validation
    
    [02:06:59 Debug] IdentityServer4.Validation.TokenRequestValidator
    client credentials token request validation success
    
    [02:06:59 Information] IdentityServer4.Validation.TokenRequestValidator
    Token request validation success, {"ClientId": "client", "ClientName": null, "GrantType": "client_credentials", "Scopes"
    : "api1", "AuthorizationCode": null, "RefreshToken": null, "UserName": null, "AuthenticationContextReferenceClasses": nu
    ll, "Tenant": null, "IdP": null, "Raw": {"grant_type": "client_credentials", "client_id": "client", "client_secret": "**
    *REDACTED***"}, "$type": "TokenRequestValidationLog"}
    
    [02:06:59 Debug] IdentityServer4.Services.DefaultClaimsService
    Getting claims for access token for client: client
    
    [02:06:59 Debug] IdentityServer4.Endpoints.TokenEndpoint
    Token request success.
    

    通过 IdentityServer4 提供的扩展方法 AddClientStore() ,还可以使用 IdentityServer4 来完成服务的注册。

                var builder = services.AddIdentityServer()
                    .AddInMemoryIdentityResources(Config.Ids)
                    .AddInMemoryApiResources(Config.Apis)
                    .AddClientStore<CustomClientStore>();
    

    祝你顺利!

  • 相关阅读:
    Iphone [Tab Bar实现多view切换,Picker,DataPicter实现
    基于socket、多线程的客户端服务器端聊天程序
    C/C++面试题
    Unity3D打Box游戏
    Unity3D项目开发一点经验
    Unity3D使用过程中常见的20个问题
    @property中strong跟weak的区别
    java多线程系列8 高级同步工具(2)CountDownLatch
    java多线程系列7 高级同步工具(1)信号量Semaphore
    java多线程系列6 synchronized 加强版 ReentrantLock
  • 原文地址:https://www.cnblogs.com/haogj/p/12621131.html
Copyright © 2011-2022 走看看