認證授權:IdentityServer4

前言

  上一篇文章<學習OIDC>介紹了OIDC協議,本篇開始我們就來具體來學習OIDC的具體實現IdentityServer4 學習。

一、IdentityServer4 是什麼?

   IdentityServer4是用於ASP.NET Core的OpenID Connect和OAuth 2.0框架。

  可以構建(或重新使用)包含登錄和註銷頁面的應用程式,IdentityServer中間件會向其添加必要的協議頭,以便客戶端應用程式可以使用這些標準協議與其對話。

   

   可以在應用程式中使用以下功能:

    • 身份驗證即服務

      所有應用程式(Web,本機,移動,服務)的集中式登錄邏輯和工作流。IdentityServer是OpenID Connect 的官方認證實現

    • 單點登錄/退出

      多種應用程式類型的單點登錄(註銷)

    • API的訪問控制

      為各種類型的客戶端(例如,伺服器到伺服器,Web應用程式,SPA和本機/移動應用程式)的API發出訪問令牌

    • 聯合網關

      支援外部身份提供程式,例如Azure Active Directory,Google,Facebook等。這使您的應用程式免受如何連接到這些外部提供程式的詳細資訊的影響。

二、簡單使用示例

先創建項目目錄結構(如下圖)

 1、IdentityServer 認證服務實現

  a) 創建一個空的WebApi項目-cz.IdentityServer,並添加IdentityServer4項目引用:如下圖:

Install-Package IdentityServer4

  b) 要啟用IdentityServer服務,不僅要把 IdentityServer 註冊到容器中, 還需要配置一下內容:

    •  Authorization Server 保護了哪些 API (資源);
    • 哪些客戶端 Client(應用) 可以使用這個 Authorization Server;

    • 指定可以使用 Authorization Server 授權的 Users(用戶)

    創建文件 InMemoryConfig.cs,用於設置以上相關內容:    

using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace cz.IdentityServer
{
    public class InMemoryConfig
    {
        public static IEnumerable<IdentityResource> GetIdentityResourceResources()
        {
            return new List<IdentityResource>
            {
                //必須要添加,否則報無效的scope錯誤
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }

        /// <summary>
        /// api資源列表
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            //可訪問的API資源(資源名,資源描述)
            return new List<ApiResource>
            {
                new ApiResource("goods", "Goods Service"),
                new ApiResource("order", "Order Service")
            };
        }

        /// <summary>
        /// 客戶端列表
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientId = "clientGoods", //訪問客戶端Id,必須唯一
                    //使用客戶端授權模式,客戶端只需要clientid和secrets就可以訪問對應的api資源。
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes = {
                        "goods",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    },
                },
                new  Client
                {
                    ClientId = "clientOrder",
                    ClientSecrets = new [] { new Secret("secret".Sha256()) },
                    //這裡使用的是通過用戶名密碼和ClientCredentials來換取token的方式. ClientCredentials允許Client只使用ClientSecrets來獲取token. 這比較適合那種沒有用戶參與的api動作
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = {
                        "order","goods",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    }
                }
            };
        }

        /// <summary>
        /// 指定可以使用 Authorization Server 授權的 Users(用戶)
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TestUser> Users()
        {
            return new[]
            {
                    new TestUser
                    {
                        SubjectId = "1",
                        Username = "cba",
                        Password = "cba"
                    },
                    new TestUser
                    {
                        SubjectId = "2",
                        Username = "chaney",
                        Password = "123"
                    }
            };
        }
    }
}

View Code

    GetApiResources:這裡指定了name和display name, 以後api使用authorization server的時候, 這個name一定要一致

    GetClients: 認證客戶端列表

    Users: 這裡的記憶體用戶的類型是TestUser, 只適合學習和測試使用, 實際生產環境中還是需要使用資料庫來存儲用戶資訊的, 例如接下來會使用asp.net core identity. TestUser的SubjectId是唯一標識.

   在Startup.cs中啟用IdentityServer服務

       修改StartUp.cs中的ConfigureServices方法

public void ConfigureServices(IServiceCollection services)
{
            services.AddControllersWithViews();
            services.AddIdentityServer()
              .AddDeveloperSigningCredential()
              .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
              .AddTestUsers(InMemoryConfig.Users().ToList())
              .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResourceResources())
              .AddInMemoryClients(InMemoryConfig.GetClients());
}

    修改StartUp.cs中的Configure方法    

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
       //啟用IdentityServer
            app.UseIdentityServer();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }

   運行此項目,打開瀏覽器訪問//localhost:5600/.well-known/openid-configuration你將會看到IdentityServer的各種元數據資訊。

    

  c) 引入QuickStartUI介面

   IdentityServer提供了一套UI以使我們能快速的開發具有基本功能的認證/授權介面,下載地址:QuickStartUI

   下載後,把QuickStartUI中:wwwroot、Quickstart、Views拷貝到項目中,如下結構:、

   

   修改Statup.cs內容如下:

public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit //go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            services.AddIdentityServer()
              .AddDeveloperSigningCredential()
              .AddTestUsers(InMemoryConfig.Users().ToList())
              .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
              .AddInMemoryClients(InMemoryConfig.GetClients());
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseStaticFiles();

            app.UseIdentityServer();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }

View Code

   運行如下效果:

   

 2、IdentityServer 集成Api服務

   a)添加web api項目cz.Api.Order,並添加nuget中安裝IdentityServer4.AccessTokenValidation ,如下圖:  

命令:Install-Package IdentityServer4.AccessTokenValidation 

    

 

   b) 修改StartUp.cs文件    

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            //IdentityServer
            services.AddMvcCore()
                    .AddAuthorization();

            //配置IdentityServer
            services.AddAuthentication("Bearer")
                        .AddIdentityServerAuthentication(options =>
                        {
                            options.RequireHttpsMetadata = false; //是否需要https
                            options.Authority = $"//localhost:5600";  //IdentityServer授權路徑
                            options.ApiName = "order";  //需要授權的服務名稱
                        });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();
       //啟用Authentication中間件
            app.UseAuthentication();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

    c) 對cz.Api.Order項目中WebApi添加[Authorize]特性    

[Route("identity")]
[ApiController]
[Authorize]
 public class IdentityController : ControllerBase
{
}

   d) 此時調用該服務時提示401,如下圖:

    

 

   e) 可以通過client資訊獲取token,然後通過Header傳遞token 調用weapi

  

 

    

三、總結

  通過上面的例子,很簡單就實現了WepApi的認證授權效果;主要步驟如下:

  • 前往IdentityServer服務中,設置需要請求的ApiResource,Client,User內容
  • WebApi服務設置IdentityServerAuthentication地址(對接認證服務)
  • 調用WebApi時,先在IdentityServer中根據設置的方式獲取access token,再帶著access token請求介面才能正常訪問

四、後續

  IdentityServer包含的內容有很多,準備從多個內容來學習記錄使用IdentityServer的功能:SSO(單點登錄)、各種授權模式使用、三方帳號登錄……

  最後來實現一個自己的統一身份認證服務