Asp.Net Core 中IdentityServer4 授權中心之應用實戰

来源:https://www.cnblogs.com/jlion/archive/2020/03/11/12447081.html
-Advertisement-
Play Games

查閱了大多數相關資料,搜索到的IdentityServer4 的應用博客大多是比較簡單並且多是翻譯官網的文檔編寫的,我這裡在 Asp.Net Core 中IdentityServer4 的應用分析中以電商系統架構升級過程中普遍遇到的場景進行實戰性講述分析,同時最後會把我的實戰性的代碼放到github... ...


一、前言

查閱了大多數相關資料,查閱到的IdentityServer4 的相關文章大多是比較簡單並且多是翻譯官網的文檔編寫的,我這裡在
Asp.Net Core 中IdentityServer4 的應用分析中會以一個電商系統架構升級過程中普遍會遇到的場景進行實戰性講述分析,同時最後會把我的實戰性的代碼放到github 上,敬請大家關註!

這裡就直接開始擼代碼,概念性東西就已經不概述了,想要瞭解概念推薦大家查看我之前的文章和官方文檔:

二、應用實戰

2.1 模擬場景

最初小團隊的電商系統場景如下圖:

這張架構圖缺點:

  • 發佈頻繁,發佈影響整個電商系統
  • 很難做到敏捷開發
  • 維護性可能會存在一定的弊端,主要看內部架構情況。

大多數小電商團隊對於多客戶端登錄授權來說可能已經實現了Oauth 2.0 的身份授權驗證,但是是和電商業務集成在一個網關裡面,這樣不是很好的方式;由於公司業務橫向擴大,產品經理調研了代理商業務,最終讓技術開發代理商業務系統。架構師出於後續發展的各方面考慮,把代理商業務單獨建立了一個獨立的網關,並且把授權服務一併給獨立出來,調整後的電商系統架構圖如下:

身份授權從業務系統中拆分出來後,有瞭如下的優勢:

  • 授權服務不受業務的影響,如果業務網關宕機了,那至少不會影響代理商網關的業務授權系統的使用
  • 授權服務一旦建立,一般就很難進行升級,除非特殊情況。
  • 在敏捷開發中,業務系統可能發佈頻繁,電商業務系統可能每天都是在頻繁升級更新,這樣也不至於影響了授權系統服務導致代理商業務受到影響

代理商業務引入進來後,同時又增加了秒殺活動,發現成交量大大增大,支付訂單集中在某一時刻翻了十幾倍,這時候整個電商業務API網關已經扛不住了,負載了幾台可能也有點吃力;開發人員經過跟架構師一起討論,得出了扛不住的原因:主要是秒殺活動高併發的支付,以至於整個電商業務系統受到影響,故準備把支付系統從業務系統中拆分出成獨立的支付網關,並做了一定的負載,成功解決了以上問題,這時候整個電商系統架構圖就演變成如下:

支付網關服務抽離後的優勢:

  • 支付網關服務更新不會太頻繁,可以減少整個系統的因為發佈導致的一系列問題,增強穩定性
  • 支付系統出現宕機不影響整個電商系統的使用,用戶還可以瀏覽商品等等其他操作,技術和運維人員也比較好排查定位問題所在;提升用戶體驗,同時提升排查問題的效率。

授權中心:單獨一個服務網關,訪問支付業務網關電商業務網關代理商業務網關都需要先通過授權中心獲得授權拿到訪問令牌AccessToken 才能正常的訪問這些網關,這樣授權模塊就不會受任何的業務影響,同時各個業務網關也不需要寫同樣的授權業務的代碼;業務網關僅僅只需關註本身的業務即可,授權中心僅僅只需要關註維護授權;經過這樣升級改造後整個系統維護性得到很大的提高,相關的業務也可以針對具體情況進行選擇性的擴容。

上面的電商網關演變架構圖中我這裡沒有畫出具體的請求流向,偷了個賴,這裡還是先把OAuth2.0 的授權大體的流程圖單獨貼出來:

由於授權網關服務之前單獨抽離出來了,這次把支付業務網關拆分出來就也比較順利,一下子就完成了電商系統的架構升級。今天這篇文章的目的架構升級也就完成了,想要深入後續電商系統架構升級的同學可以關註後續給大家帶來的微服務的相關教程,到時繼續以這個例子來進行微服務架構上的演變升級,敬請大家關註。好了下麵我們來回歸該升級的和核心主題授權網關服務 IdentityServer4 的應用。

2.2 IdentityServer4 密碼授權模式

授權網關服務

靜態記憶體配置方式

定義資源

分資源分為身份資源(Identity resources)和API資源(API resources)。
我們先創建Jlion.NetCore.Identity.Service 網關服務項目,在網關服務中添加受保護的API資源,創建OAuthMemoryData 類代碼如下:

/// <summary>
/// Api資源 靜態方式定義
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
       return new List<ApiResource>
       {
            new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName),
       };
}

定義客戶端Client

接下來OAuthMemoryData 類中定義一個客戶端應用程式的Client,我們將使用它來訪問我們的API資源代碼如下:

public static IEnumerable<Client> GetClients()
{
       return new List<Client>
       {
           new Client()
           {
               ClientId =OAuthConfig.UserApi.ClientId,
               AllowedGrantTypes = new List<string>()
               {
                   GrantTypes.ResourceOwnerPassword.FirstOrDefault(),//Resource Owner Password模式
               },
               ClientSecrets = {new Secret(OAuthConfig.UserApi.Secret.Sha256()) },
               AllowedScopes= {OAuthConfig.UserApi.ApiName},
               AccessTokenLifetime = OAuthConfig.ExpireIn,
           },
      };
 }
  • AllowedGrantTypes :配置授權類型,可以配置多個授權類型
  • ClientSecrets:客戶端加密方式
  • AllowedScopes:配置授權範圍,這裡指定哪些API 受此方式保護
  • AccessTokenLifetime:配置Token 失效時間
  • GrantTypes:授權類型,這裡使用的是密碼模式ResourceOwnerPassword

代碼中可以看到有一個OAuthConfig 類,這個類是我單獨建的,是用於統一管理,方便維護,代碼如下:

 public class OAuthConfig
 {
        /// <summary>
        /// 過期秒數
        /// </summary>
        public const int ExpireIn = 36000;

        /// <summary>
        /// 用戶Api相關
        /// </summary>
        public static class UserApi
        {
            public static string ApiName = "user_api";

            public static string ClientId = "user_clientid";

            public static string Secret = "user_secret";
        }
 }

如果後續架構升級,添加了其他的網關服務,則只需要在這裡添加所需要保護的API 資源,也可以通過讀取資料庫方式讀取受保護的Api資源。

接下來OAuthMemoryData 類添加測試用戶,代碼如下:

/// <summary>
/// 測試的賬號和密碼
/// </summary>
/// <returns></returns>
public static List<TestUser> GetTestUsers()
{
    return new List<TestUser>
    {
        new TestUser()
        {
             SubjectId = "1",
             Username = "test",
             Password = "123456"
        }
    };
}

上面受保護的資源,和客戶端以及測試賬號都已經建立好了,現在需要把IdentityServer4 註冊到DI中:
Startup 中的ConfigureServices 代碼如下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    #region 記憶體方式
    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
        .AddInMemoryClients(OAuthMemoryData.GetClients())
        .AddTestUsers(OAuthMemoryData.GetTestUsers());
    #endregion

}

代碼解讀:

  • AddDeveloperSigningCredential:添加證書加密方式,執行該方法,會先判斷tempkey.rsa證書文件是否存在,如果不存在的話,就創建一個新的tempkey.rsa證書文件,如果存在的話,就使用此證書文件。
  • AddInMemoryApiResources:把受保護的Api資源添加到記憶體中
  • AddInMemoryClients :客戶端配置添加到記憶體中
  • AddTestUsers :測試的用戶添加進來

最後通過UseIdentityServer()需要把IdentityServer4 中間件添加到Http管道中,代碼如下:

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

     app.UseRouting();

     app.UseAuthorization();

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

好了,現在授權網關服務代碼已經完成,現在直接通過命令行方式啟動,命令行啟動如下,我指定5000埠,如下圖:

電商用戶網關Api項目

現在我來新建一個WebApi 大的用戶網關服務項目,取名為Jlion.NetCore.Identity.UserApiService,新建後會預設有一個天氣預報的api介面,代碼如下:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

接下來在Startup 類中添加授權網關服務的配置到DI中,代碼如下:

 public void ConfigureServices(IServiceCollection services)
 {
       services.AddControllers();

       services.AddAuthorization();
       services.AddAuthentication("Bearer")
           .AddIdentityServerAuthentication(options =>
           {
               options.Authority = "http://localhost:5000";    //配置Identityserver的授權地址
               options.RequireHttpsMetadata = false;           //不需要https    
               options.ApiName = OAuthConfig.UserApi.ApiName;  //api的name,需要和config的名稱相同
           });
  }

這裡的options.ApiName 需要和網關服務中的Api 資源配置中的ApiName 一致

接下來需要把授權和認證中間件分別註冊到Http 管道中,代碼如下:

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


    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

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

現在授權服務網關啟用已經完成,只需要在需要保護的Controller 中添加 Authorize 過濾器即可,現在我也通過命令行把需要保護的網關服務啟動,如圖:

現在我通過postman 工具來單獨訪問 用戶網關服務API,不攜帶任何信息的情況下,如圖:

從訪問結果可以看出返回401 Unauthorized 未授權。

我們接下來再來訪問授權服務網關,如圖:

請求網關服務中body中攜帶了用戶名及密碼等相關信息,這是返回了access_token 及有效期等相關信息,我們再拿access_token 來繼續上面的操作,訪問用戶業務網關的介面,如圖:

訪問結果中已經返回了我們所需要的介面數據,大家目前已經對密碼模式的使用有了一定的瞭解,但是這時候可能會有人問我,我生產環境中可能需要通過資料庫的方式進行用戶信息的判斷,以及客戶端授權方式需要更加靈活的配置,可通過後臺來配置ClientId以及授權方式等,那應該怎麼辦呢?下麵我再來給大家帶來生存環境中的實現方式。

資料庫匹配驗證方式

我們需要通過用戶名和密碼到資料庫中驗證方式則需要實現IResourceOwnerPasswordValidator 介面,並實現ValidateAsync 驗證方法,簡單的代碼如下:

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        try
        {
            var userName = context.UserName;
            var password = context.Password;

            //驗證用戶,這麼可以到資料庫裡面驗證用戶名和密碼是否正確
            var claimList = await ValidateUserAsync(userName, password);

            // 驗證賬號
            context.Result = new GrantValidationResult
            (
                subject: userName,
                authenticationMethod: "custom",
                claims: claimList.ToArray()
             );
       }
       catch (Exception ex)
       {
            //驗證異常結果
            context.Result = new GrantValidationResult()
            {
                IsError = true,
                Error = ex.Message
             };
       }
  }

    #region Private Method
    /// <summary>
    /// 驗證用戶
    /// </summary>
    /// <param name="loginName"></param>
    /// <param name="password"></param>
    /// <returns></returns>
    private async Task<List<Claim>> ValidateUserAsync(string loginName, string password)
    {
        //TODO 這裡可以通過用戶名和密碼到資料庫中去驗證是否存在,
        // 以及角色相關信息,我這裡還是使用記憶體中已經存在的用戶和密碼
        var user = OAuthMemoryData.GetTestUsers();

        if (user == null)
            throw new Exception("登錄失敗,用戶名和密碼不正確");

        return new List<Claim>()
        {
            new Claim(ClaimTypes.Name, $"{loginName}"),
        };
    }
    #endregion
}

用戶密碼驗證器已經實現完成,現在需要把之前的通過AddTestUsers 方式改成AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() 方式,修改後的代碼如下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    #region 資料庫存儲方式
    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
        .AddInMemoryClients(OAuthMemoryData.GetClients())
        //.AddTestUsers(OAuthMemoryData.GetTestUsers());
        .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();
   #endregion
}

目前已經實現了用戶名和密碼資料庫驗證的方式,但是現在有人會考慮另外一個場景,客戶端的授權方式等也需要通過後臺可配置的方式,這樣比較靈活,不通過代碼中靜態配置的方式,那應該這麼辦呢?
官方考慮的很周到,我們可以使用IClientStore 介面,同時需要實現FindClientByIdAsync 方法,代碼如下:

public class ClientStore : IClientStore
{
    public async Task<Client> FindClientByIdAsync(string clientId)
    {
        #region 用戶名密碼
        var memoryClients = OAuthMemoryData.GetClients();
        if (memoryClients.Any(oo => oo.ClientId == clientId))
        {
           return memoryClients.FirstOrDefault(oo => oo.ClientId == clientId);
        }
        #endregion

        #region 通過資料庫查詢Client 信息
        return GetClient(clientId);
        #endregion
    }

    private Client GetClient(string client)
    {
        //TODO 根據資料庫查詢
        return null;
    }
}

StartupConfigureServices 代碼AddInMemoryClients 改成AddClientStore<> 代碼如下:

public void ConfigureServices(IServiceCollection services)
{
     services.AddControllers();

     #region 資料庫存儲方式
     services.AddIdentityServer()
         .AddDeveloperSigningCredential()
         .AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
         //.AddInMemoryClients(OAuthMemoryData.GetClients())
         .AddClientStore<ClientStore>()
         .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();
    #endregion
 }

好了資料庫查詢匹配方式也已經改造完了,業務網關服務不需要改動如何代碼,運行結果這裡就不在運行演示了。Demo 代碼已經上傳到github 上了,github 源代碼地址https://github.com/a312586670/IdentityServerDemo

結語:通過IdentityServer4 實現的簡單授權中心的思想也就完成了,後續繼續學習,有錯誤地方還請留言指出!感謝!!!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 public string ExcelFile() 2 { //指定文件路徑, 3 string fileName=@"d:\Stu.xls"; //創建一個文件流,並指定其中屬性 4 using(FileStream fs = new FileStream(fileName,FileMode. ...
  • 場景 效果 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載。 實現 新建一個用戶控制項TransparencyButton,修改其代碼如下 using System; using S ...
  • 有些操作需要管理員許可權,比如寫註冊表,所以就有了以下代碼: 然後又問你是否同意,都是用戶賬戶控制(UAC)惹的事,我明明是管理員身份證登錄windows的,需要我再次確認,就想著能不能把UAC關閉了,發現是可以的: 1. 按鍵盤的“視窗+R”,在運行視窗中輸入“gpedit.msc”回車,打開“本地 ...
  • 第一次用vs12和ao10.2調試addin時,提示無法命中斷點,查看幫助設置了arcmap的net版本後,即可順利調試。 今天vs17調試時發現又不能命中斷點,又勉強返回vs12調試,不是很方便 嘗試後,如下方式目前可用: 1.選擇自動 2.選擇托管 返回vs2012後,發現選擇類型不同也會影響是 ...
  • 使用openxml提取word中的文本和圖片 使用 openXml 提取 word 中的 Text 和 Drawing 使用 openXml 將 word 中的文本和圖片轉為Html 使用 openXml 將 word 中的 文本 和 圖片 轉為 Html 註:只支持內嵌,不支持公式 文章最後為效果 ...
  • 1. 索引和範圍 以下 .NET 類型同時支持索引和範圍:Array、String、Span 和 ReadOnlySpan。 List 支持索引,但不支持範圍 例一、獲取身份證號碼的生日 例二、獲取字元串最後一位的內容 例三、移除最後最後一位的內容 2. switch 表達式 屬性模式 元組模式 位 ...
  • 場景 RichTextBox控制項允許用戶輸入和編輯文本的同時提供了比普通的TextBox控制項更高級的格式特征。 效果 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載。 實現 新建一 ...
  • nuget預設的全局包下載地址一般為:C:\Users\{UserName}\.nuget\packages 項目多了之後,nuget下載的包就回慢慢的變多,導致c盤被大量占用,這時候我們想要將nuget的預設的包存放位置放在其其他的目錄下麵。 可以通過修改globalPackagesFolder配... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...