1. 程式人生 > >Azure AD(二)呼叫受Microsoft 標識平臺保護的 ASP.NET Core Web API 上

Azure AD(二)呼叫受Microsoft 標識平臺保護的 ASP.NET Core Web API 上

一,引言

  上一節講到Azure AD的一些基礎概念,以及Azure AD究竟可以用來做什麼?本節就接著講如何在我們的專案中整合Azure AD 保護我們的API資源(其實這裡還可以在 SPA單頁面應用,Web專案,移動/桌面應用程式整合Azure AD),好了,廢話不多說,開始今天的內容。

二,正文

上一篇介紹到 Azure AD 其實是微軟基於雲的表示和授權訪問管理服務,它可以幫助我們在Azure中登入和訪問資源。我們可以通過Azure的標識平臺生成應用程式,採用微軟表示登入,以及獲取令牌來呼叫受保護的API資源。也就是說這一切功能也是基於包含Oauth 2.0和Open ID Connect的身份驗證服務。

下面先去了解,熟悉一下關於Identity Server 4的 OpenID 和 OAuth 的區別以及授權模式

如果之前有了解 Identity Server 4 這種授權驗證的框架,可以跳過下面的介紹:

identityServer4 知多少(聖傑):https://www.cnblogs.com/sheng-jie/p/9430920.html

授權伺服器identityServer4 開篇(老張的哲學):https://www.cnblogs.com/laozhang-is-phi/p/10483922.html

(一) OpenID 和 OAuth 的區別 (以下的介紹來自google和 OAuth官網)

  1,OpenID 是一個以使用者為中心的數字身份識別框架,它具有開放、分散性。OpenID 的建立基於這樣一個概念:我們可以通過 URI (又叫 URL 或網站地址)來認證一個網站的唯一身份,簡單通俗的理解,OpenID是用來做為身份驗證的

  2,OAuth 2.0是用於授權的行業標準協議。OAuth 2.0致力於簡化客戶端開發人員的工作,同時為Web應用程式,桌面應用程式,行動電話和客廳裝置提供特定的授權流程。也就是說 OAuth 2.0 是用來進行授權的

  3,OpenID Connect 是基於OAuth 協議的簡單身份層。它允許客戶端基於授權伺服器執行的身份驗證來驗證終端使用者的身份,並以可互操作且類似於REST的方式獲取有關最終​​使用者的基本配置檔案資訊。OpenID Connect允許所有型別的客戶端(包括基於Web的客戶端,移動客戶端和JavaScript客戶端)請求並接收有關經過身份驗證的會話和終端使用者的資訊。規範套件是可擴充套件的,允許參與者在對他們有意義的時候使用可選功能,例如身份資料加密,OpenID提供程式的發現以及會話管理。

  OpenID Connect執行許多與OpenID 2.0相同的任務,但是這樣做的方式是API友好的,並且可由本機和移動應用程式使用,OpenID Connect定義了用於可靠簽名和加密的可選機制。OAuth 1.0a和OpenID 2.0的整合需要擴充套件,而在OpenID Connect中,OAuth 2.0功能與協議本身整合在一起。

(二)授權模式

  1,隱式模式(Implicit Flow)

  2,客戶端授權模式(Client Credentials Flow)

  3,授權碼授權模式(Authorization Code Flow)

  4,資源持有者密碼模式(Resource Owner Password Credentials ):注意一下,這裡的密碼翻譯的不正確,應該是單單指密碼,證書也是可以的

  。。。。。等

這裡暫時只瞭解這四種常見的授權模式。

(三)新增受保護資源

1,VS 建立 “Asp.Net Core WebApi” 專案,並且新增 “OrderController” 控制器,並且新增相應的方法,此步驟暫時省略,詳細程式碼我整理完成後,會新增到github上

2,安裝:Microsoft.AspNetCore.Authentication.AzureAD.UI

3,需要註冊驗證服務,整個地方預設的是 “AzureADJwtBearer”,AddAzureADBearer方法繫結Azure AD身份驗證終結點,租戶,租戶所在的自定義域,以及客戶端Id

services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
         .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

開啟Authentication中介軟體

 // open authentication middleware
     app.UseAuthentication();

4,在Azure Portal 上新增一個租戶

  4.1 在Azure Portal 上選擇 選單 “Azure Active Directory”

              

  4.2,點選圖中的 “建立目錄”

      

  4.3,目錄選擇預設 “Azure Active Directory”,點選 “下一步-配置”

         

  4.4,新增對應的組織名稱和初始域名,

    組織名:myCommpany

    初始域名:trainingdiscussion 

 

   點選 “產看+建立” 進行驗證,驗證完成後點選 “建立” 

5,註冊 “應用程式”

  5.1,Azure Portal 點選個人頭像,切換目錄

         

  5.2,選中剛剛建立的 “MyCompany” 的目錄,繼續在Portal首頁左側選擇 “Azure Active Directory”,選中 “應用註冊” ,點選 “新註冊”    

  5.3,填寫應用註冊的一些基本資訊

    (1)新增受保護的Api資源的名稱,也就是我們在VS中建立的.Net Core 的 WebApi 專案,我這裡暫時命名為 “WebApi”,

    (2)選擇支援的賬戶型別,我這裡選擇的是一個多租戶的型別

    (3)平臺配置,選擇 Web API,這裡的平臺配置怎麼理解:就好在Web專案中是在成功驗證使用者身份後,會攜帶令牌,我們作為目標接受的URL,稱其為 ”回撥地址“

           

  5.4, 點選 ”註冊“,然後選擇 ”管理“---》”身份驗證“,點選”切換到舊體驗“

  5.5,找到隱式授權模式,勾選 ”訪問令牌“,”ID令牌“兩個複選框

             

  OK,以上我們在Azure Portal 就配置好一個客戶端的註冊,

  5.6,在此,我們真正在程式碼中開啟驗證的話,還需要4個引數,也就是上面提到的 ”自定義域(Domain)“,”租戶Id(TenantId)“,”客戶端Id(ClientId)“,”應用註冊終結點(Instance)“

  (1)Domain,TenantId (Domain 引數可以在建立目錄時,先行復制好)

    

  (2)ClientId:選擇剛剛註冊好的應用程式,進入應用程式頁面後,找到物件Id 進行復制操作。

  

 

 

  (3)Instance:每個國家都有一個單獨的Azure門戶。若要在應用程式中與Azure AD進行整合,需要在每個特定環境的Azure門戶中單獨註冊應用程式。

    所有用於驗證應用程式的Azure AD終結點的URL也是不同的

    適用於美國政府的 Azure AD  :https://login.microsoftonline.us

    Azure AD 德國                        :https://login.microsoftonline.de

    由世紀互聯運營的 Azure AD 中國:https://login.chinacloudapi.cn

    Azure AD(全球服務)          :https://login.microsoftonline.com

 

  例如,對於 Azure 中國:

  • 授權常用終結點為:https://login.chinacloudapi.cn/common/oauth2/authorize
  • 令牌常用終結點為 :https://login.chinacloudapi.cn/common/oauth2/token

  對於單租戶應用程式,請將先前 URL 中的“common”替換為你的租戶 ID 或名稱。 示例為 https://login.chinacloudapi.cn/myCommany。

6,配置檔案中的內容如下所示

"AzureAd": {
    "Instance": "https://login.chinacloudapi.cn/",
    "Domain": "trainingdiscussion.partner.onmschina.cn",
    "TenantId": "53359126-8bcf-455d-a934-5fe72d349207",
    "ClientId": "f38ec09d-203e-4b2d-a1c1-faf76a608528"
  },

給需要驗證的方法或者控制器加上驗證標籤[Authorize]

詳情請看完整程式碼

完整程式碼:

public class Startup
    {
        public Startup(IConfiguration configuration, IWebHostEnvironment environment)
        {
            Configuration = configuration;
            Environment = environment;
        }

        public IConfiguration Configuration { get; }

        public IWebHostEnvironment Environment { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton(new Appsettings(Environment.ContentRootPath));

            services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
                .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Description = "JWT授權(資料將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間是一個空格)\"",
                    Type = SecuritySchemeType.OAuth2,
                    In = ParameterLocation.Header,//jwt預設存放Authorization資訊的位置(請求頭中)
                    Flows = new OpenApiOAuthFlows()
                    {
                        Implicit = new OpenApiOAuthFlow
                        {
                            Scopes = new Dictionary<string, string>
                            {
                                { "user_impersonation", "Access API" }
                            },
                            AuthorizationUrl = new Uri($"https://login.chinacloudapi.cn/{ Appsettings.app(new string[] { "AzureAD", "TenantId" })}/oauth2/authorize")
                            
                        }
                    }
                });
                // 在header中新增token,傳遞到後臺
                c.OperationFilter<SecurityRequirementsOperationFilter>();
            });

            services.AddControllers();
        }

        // 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();
            }

            #region Swagger
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                //根據版本名稱倒序 遍歷展示
                var ApiName = Appsettings.app(new string[] { "Startup", "ApiName" });
                c.SwaggerEndpoint($"/swagger/v1/swagger.json", $"{ApiName} v1");

                c.OAuthClientId(Appsettings.app(new string[] { "Swagger", "ClientId" }));
                c.OAuthClientSecret(Appsettings.app(new string[] { "Swagger", "ClientSecret" }));
                c.OAuthRealm(Appsettings.app(new string[] { "AzureAD", "ClientId" }));
                c.OAuthAppName("My API V1");
                c.OAuthScopeSeparator(" ");
                c.OAuthAdditionalQueryStringParams(new Dictionary<string, string>() { { "resource", Appsettings.app(new string[] { "AzureAD", "ClientId" }) } });
            });
            #endregion

            // open authentication middleware
            app.UseAuthentication();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
startup.cs
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "AzureAd": {
    "Instance": "https://login.chinacloudapi.cn/",
    "Domain": "trainingdiscussion.partner.onmschina.cn",
    "TenantId": "53359126-8bcf-455d-a934-5fe72d349207",
    "ClientId": "f38ec09d-203e-4b2d-a1c1-faf76a608528"
  },
  "Swagger": {
    "ClientId": "e15070c3-7e9a-40c0-b73f-2f34fb031641",
    "ClientSecret": "" //  ?fxV/=/pwlRjwQgoIdLRlPNlWBBQ8939
  }
}
application.json
    [Route("api/[controller]")]
    [ApiController]

    public class OrderController : ControllerBase
    {
        // GET: api/Order
        [HttpGet]
        [Authorize]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET: api/Order/5
        [HttpGet("{id}", Name = "Get")]
        public string Get(int id)
        {
            return "value";
        }

        // POST: api/Order
        [HttpPost]
        public void Post([FromBody] string value)
        {
        }

        // PUT: api/Order/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE: api/ApiWithActions/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
OrderController.cs

7,專案新增Swagger的配置,使用Swagger進行介面測試-

  7.1:安裝  Swashbuckle.AspNetCore,Swashbuckle.AspNetCore.Filters

  7.1:配置 Swagger 服務,並且使用隱式授權模式

services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Description = "JWT授權(資料將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間是一個空格)\"",
                    Type = SecuritySchemeType.OAuth2,
                    In = ParameterLocation.Header,//jwt預設存放Authorization資訊的位置(請求頭中)
                    Flows = new OpenApiOAuthFlows()
                    {
                        Implicit = new OpenApiOAuthFlow
                        {
                            AuthorizationUrl = new Uri($"https://login.chinacloudapi.cn/{ Appsettings.app(new string[] { "AzureAD", "TenantId" })}/oauth2/authorize")
                            
                        }
                    }
                });
                // 在header中新增token,傳遞到後臺
                c.OperationFilter<SecurityRequirementsOperationFilter>();
            });

  7.3,開啟中介軟體

 app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                //根據版本名稱倒序 遍歷展示
                var ApiName = Appsettings.app(new string[] { "Startup", "ApiName" });
                c.SwaggerEndpoint($"/swagger/v1/swagger.json", $"{ApiName} v1");

                c.OAuthClientId(Appsettings.app(new string[] { "Swagger", "ClientId" }));
                c.OAuthClientSecret(Appsettings.app(new string[] { "Swagger", "ClientSecret" }));
                c.OAuthRealm(Appsettings.app(new string[] { "AzureAD", "ClientId" }));
                c.OAuthAppName("My API V1");
                c.OAuthScopeSeparator(" ");
                c.OAuthAdditionalQueryStringParams(new Dictionary<string, string>() { { "resource", Appsettings.app(new string[] { "AzureAD", "ClientId" }) } });
            });

詳細程式碼,請看上面的的完整程式碼☝☝☝☝☝

  7.4,註冊應用程式(Swagger)

  (1)現在,我們將為Swagger新增一個 "Azure AD" 應用程式,並授予它向 "Web API" 應用程式發出請求的許可權

      注意重定向URL的地址,這裡需要配置 swagger 的回撥地址,localhost:9021 是專案執行的地址

    勾選啟用隱式授權模式的 ”訪問令牌“,”ID令牌“

  (2)轉到 WebApi 應用新增任意scope(scope名隨便定義),那此應用的API將會被公開(暴露),我們這裡添加了一個scope(讀)

       

  (3)將應用程式ID複製到appsettings中的Swagger:ClientId

 

 

 

  (4)轉到 “Swagger” 的應用註冊點選”新增許可權“---》“委託的許可權” 來新增下面綠框架中的兩個許可權,管理員同意後,前端應用就擁有呼叫後端API的許可權了。 

 

 

 

 

 

 

 

 

 

 

 

 

 8,測試效果

  啟動專案,在專案的 “Swagger” 首頁,點選  Try it out 嘗試呼叫 api/order 介面,Response 提示 401 無訪問許可權

 

 

 

此時,我們可以在Swagger首頁點選 ”Authorize“ ,驗證和訪問Api資源

 

 

 

登陸Azure賬戶,進行認證授權

 

 

 

再次呼叫 api/Order 介面  Response:200 OK