IdentityServer4:授權碼模式

IdentityServer4:授權碼模式

授權碼模式比較適用於既有前端又有後端代碼的項目,比如:想 AspNetCore MVC 客戶端。

授權碼模式的流程是:
用戶訪問客戶端,客戶端調轉到認證服務器登錄頁面,讓用戶輸入用戶名和密碼,並點擊授權後,到一個 code(授權碼),並放在回調URL中,
客戶端再從這個回調的URL中解析到得到code(授權碼),再通過code(授權碼)向服務器發送請求獲取 access_token。

授權碼模式比簡化(隱藏)模式多了一步:獲取 code(授權碼)。

Api 資源項目

創建項目

打開 VS,創建一個“AspNet Core WebApi” 項目, 名爲:Dotnet.WebApi.Ids4.CustomerApi

依賴包

添加依賴包

    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.3" />

添加認證方案

修改 Program.cs 爲如下代碼:


using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace Dotnet.WebApi.Ids4.CustomerApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.Title = "CustomerAPI服務器";

            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddControllers();

            builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    //IdentityServer4地址
                    options.Authority = "https://localhost:6001";
                    //認證的ApiResource名稱
                    options.Audience = "CustomerAPIResource";
                    //使用JWT認證類型
                    options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
                });

            //配置跨域。
            builder.Services.AddCors(options =>
            {
                options.AddPolicy("AppCors", policy => policy.WithOrigins("https://localhost:6021")
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowCredentials()
                );
            });

            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.Urls.Add("https://*:6011");
            app.UseHttpsRedirection();
            //啓用跨域中間件
            app.UseCors("AppCors");
            //身份驗證
            app.UseAuthentication();
            //授權
            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}

其中,
(1)添加 JWT 認證:

            builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    //IdentityServer4地址
                    options.Authority = "https://localhost:6001";
                    //認證的ApiResource名稱
                    options.Audience = "CustomerAPIResource";
                    //使用JWT認證類型
                    options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
                });

其中, 設置認證服務器:options.Authority = "https://localhost:6001";

添加 Api

新增文件:Controllers/CustomerController.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Dotnet.WebApi.Ids4.CustomerApi.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class CustomerController : ControllerBase
    {
        /// <summary>
        /// 獲取客戶信息列表。
        /// </summary>
        /// <returns></returns>
        [HttpGet("GetList")]
        public IEnumerable<Customer> GetList()
        {
            return new List<Customer>
            {
                new Customer{ Id=1, Name="客戶1", Phone="電話1"},
                new Customer{ Id=2, Name="客戶2", Phone="電話2"},
                new Customer{ Id=3, Name="客戶3", Phone="電話3"},
            };
        }
    }
}

其中:
(1)在控制器上添加特性:[Authorize],這樣只有登錄用戶才能訪問,這樣就起到保護了Api資源的目的。

Customer.cs

namespace Dotnet.WebApi.Ids4.CustomerApi
{
    /// <summary>
    /// 客戶實體模型
    /// </summary>
    public class Customer
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public string? Phone { get; set; }
    }
}

修改 Index 視圖

Views/Home/Index.cshtml

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<div style="margin:20px;">
    <a href="/home/userinfo">用戶信息</a>
    <a href="/home/apidata">調用API</a>
</div>

添加 ApiData 視圖

Views/Home/ApiData.cshtml

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<div style="margin:20px;">
    <a href="/home/userinfo">用戶信息</a>
    <a href="/home/apidata">調用API</a>
</div>

添加 UserInfo 視圖

Views/Home/UserInfo.cshtml

@using Microsoft.AspNetCore.Authentication
@{
    ViewData["Title"] = "用戶信息";
}

<h1>@ViewData["Title"]</h1>
<h2>身份聲明</h2>
<table class="table table-bordered">
    <tr>
        <td>類型</td>
        <td>值</td>
    </tr>
    @foreach (var c in User.Claims)
    {
        <tr>
            <td>@c.Type</td>
            <td>@c.Value</td>
        </tr>
    }
</table>
<h2>屬性</h2>
<table class="table table-bordered">
    <tr>
        <td>類型</td>
        <td>值</td>
    </tr>
    @foreach (var p in (await Context.AuthenticateAsync()).Properties.Items)
    {
        <tr>
            <td>@p.Key</td>
            <td>@p.Value</td>
        </tr>
    }
</table>

認證服務器

創建項目

打開 VS,創建一個“AspNet Core 空” 項目,名爲:Dotnet.WebApi.Ids4.AuthService

依賴包

添加依賴包

<PackageReference Include="IdentityServer4" Version="4.1.2" />

配置 IdentityServer4

創建文件:IdentityConfig.cs,添加如下代碼:

using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System.Security.Claims;

namespace Dotnet.WebApi.Ids4.AuthService
{
    public static class IdentityConfig
    {
        /// <summary>
        /// 配置IdentityResource。
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource> {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }

        /// <summary>
        /// 配置API作用域。
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiScope> GetApiScopes()
        {
            return new List<ApiScope>
            {
                //客戶相關API作用域
                new ApiScope("Customer.Read","讀取客戶信息。"),
                new ApiScope("Customer.Add","添加客戶信息。"),

                //共享API作用域
                new ApiScope("News","新聞信息。")
            };
        }

        /// <summary>
        /// 配置ApiResource。
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            //將多個具體的APIScope歸爲一個ApiResource。
            return new List<ApiResource>()
            {
                new ApiResource("CustomerAPIResource", "客戶資源")
                {
                    Scopes={ "Customer.Read", "Customer.Add", "News" }
                }
            };
        }

        /// <summary>
        /// 配置客戶端應用。
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            #region 授權碼模式

            return new List<Client>
            {
                new Client
                {
                    //客戶端ID。
                    ClientId="WebMvcCodeClient",
                    //客戶端名稱。
                    ClientName="WebMvc-CodeClient",
                    //客戶端密鑰。
                    ClientSecrets =
                    {
                        new Secret("WebMvcCodeClient00000001".Sha256())
                    },
                    //Code表示授權碼認證模式。
                    AllowedGrantTypes=GrantTypes.Code,
                    //是否支持授權操作頁面,true表示顯示授權界面,否則不顯示。
                    RequireConsent=true,
                    //認證成功之後重定向的客戶端地址,默認就是signin-oidc。
                    RedirectUris={ "https://localhost:6022/signin-oidc"},
                    //登出時重定向的地址,默認是signout-oidc。
                    PostLogoutRedirectUris={"https://localhost:6022/signout-callback-oidc"},
                    //是否允許返回刷新Token。
                    AllowOfflineAccess=true,
                    //指定客戶端獲取的AccessToken能訪問到的API作用域。
                    AllowedScopes={
                        "Customer.Read",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    }
                }
            };

            #endregion
        }

        /// <summary>
        /// 配置用戶。
        /// </summary>
        /// <returns></returns>
        public static List<TestUser> GetUsers()
        {
            #region 授權碼模式    
            
            return new List<TestUser>
            {
                new TestUser
                {
                        SubjectId="00003",
                        Username="zhangsan",
                        Password="123456",
                        //添加聲明信息
                        Claims =
                        {
                            new Claim(JwtClaimTypes.Name, "zhangsan"),
                            new Claim(JwtClaimTypes.GivenName, "san"),
                            new Claim(JwtClaimTypes.FamilyName, "zhang"),
                            new Claim(JwtClaimTypes.Email, "[email protected]"),
                            new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean)
                    }
                }
            };

            #endregion
        }
    

    }
}

代碼解析:

(1)授權碼模式通過在客戶端和認證服務器往返的URL來傳遞數據,使用了 Openid Connect 協議和 OAuth2.0 協議,故得在IdentityServer中配置 Openid 信息, 這是授權碼模式必須得添加的:

        /// <summary>
        /// 配置IdentityResource。
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource> {
                new IdentityResources.OpenId(),
            };
        }

如果需要客戶端要求能獲取到用戶信息,還得添加new IdentityResources.Profile(), 如下所示:

        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource> {
                new IdentityResources.OpenId(),
+               new IdentityResources.Profile()
            };
        }

(2)如下代碼添加了 Client,並將其授權模式設置爲:簡化模式, 並設置密碼,和 Scope:

                new Client
                {
                    //客戶端ID。
                    ClientId="WebMvcCodeClient",
                    //客戶端名稱。
                    ClientName="WebMvc-CodeClient",
                    //客戶端密鑰。
                    ClientSecrets =
                    {
                        new Secret("WebMvcCodeClient00000001".Sha256())
                    },
                    //Code表示授權碼認證模式。
                    AllowedGrantTypes=GrantTypes.Code,
                    //是否支持授權操作頁面,true表示顯示授權界面,否則不顯示。
                    RequireConsent=true,
                    //認證成功之後重定向的客戶端地址,默認就是signin-oidc。
                    RedirectUris={ "https://localhost:6022/signin-oidc"},
                    //登出時重定向的地址,默認是signout-oidc。
                    PostLogoutRedirectUris={"https://localhost:6022/signout-callback-oidc"},
                    //是否允許返回刷新Token。
                    AllowOfflineAccess=true,
                    //指定客戶端獲取的AccessToken能訪問到的API作用域。
                    AllowedScopes={
                        "Customer.Read",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    }
                }

其中:
(1)設置授權模式: AllowedGrantTypes=GrantTypes.Code
(2)身份認證成功之後重定向到客戶端的回調地址: RedirectUris={ "https://localhost:6022/signin-oidc"},
(3)退出時重定向到客戶端的地址:PostLogoutRedirectUris={"https://localhost:6022/signout-callback-oidc"},
(4)跨域支持: AllowedCorsOrigins={"https://localhost:6021"},
(5)設置Scope:AllowedScopes = { ... }

(3) 添加用戶:因爲授權碼模式需要用戶參與,故得添加用戶;

            return new List<TestUser>
            {
                new TestUser
                {
                        SubjectId="00001",
                        Username="Kevin",
                        Password="123456",
                        //添加聲明信息
                        Claims =
                        {
                            new Claim(JwtClaimTypes.Name, "Kevin"),
                            new Claim(JwtClaimTypes.GivenName, "Mi"),
                            new Claim(JwtClaimTypes.FamilyName, "Kala"),
                            new Claim(JwtClaimTypes.Email, "[email protected]"),
                            new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean)
                    }
                }
            };

集成 IdentityServer4

添加 IdentityServer4的Quickstart UI

因爲授權碼模式流程是:
用戶訪問客戶端,客戶端調轉到認證服務器登錄頁面,讓用戶輸入用戶名和密碼,並點擊授權後,得到一個code(授權碼),並放在回調URL中,
客戶端再從這個回調的URL中解析到得到code(授權碼),再通過code(授權碼)向服務器發送請求獲取 access_token。

從以上過程可以看到,IdentityServer4 認證服務器得有一個界面,好在已經一個開源項目:Quickstart UI,可以直接用即可。
下載 Quickstart UI:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI,
然後把 Quickstart、Views、wwwroot 三個文件夾複製到 Dotnet.WebApi.Ids4.AuthService 項目根目錄下。
由於 Quickstart UI 使用了 AspNet Core 的 MVC 框架,所以得在 Program.cs 開啓 MVC 框架:

           //註冊MVC服務。
            builder.Services.AddControllersWithViews();
            ......
            //終結點
            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");

Program.cs

修改 Program.cs 爲如下代碼:

namespace Dotnet.WebApi.Ids4.AuthService
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.Title = "認證和授權服務器";

            var builder = WebApplication.CreateBuilder(args);

            //註冊MVC服務。
            builder.Services.AddControllersWithViews();

            //註冊IdentityServer4組件
            builder.Services.AddIdentityServer()
                .AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources())
                .AddInMemoryApiScopes(IdentityConfig.GetApiScopes())
                .AddInMemoryApiResources(IdentityConfig.GetApiResources())
                .AddInMemoryClients(IdentityConfig.GetClients())
                .AddTestUsers(IdentityConfig.GetUsers())
                .AddDeveloperSigningCredential(); // 添加臨時內存中的證書

            var app = builder.Build();
            //修改端口號
            app.Urls.Add("https://*:6001");

            //啓用靜態文件
            app.UseStaticFiles();
            //啓用HTTPS轉向
            app.UseHttpsRedirection();
            //啓用路由
            app.UseRouting();
            //添加IDS4中間件。
            //在瀏覽器中輸入如下地址訪問 IdentityServer4 的發現文檔:https://localhost:6001/.well-known/openid-configuration
            app.UseIdentityServer();
            //授權
            app.UseAuthorization();

            //終結點
            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");

            app.Run();
        }
    }
}

其中,app.Urls.Add("https://*:6001"); 設置認證服務器的監聽端口爲:6001

授權碼模式客戶端

創建項目

創建一個 “AspNet Core MVC”,名爲:Dotnet.WebApi.CodeClient

依賴包

添加依賴包:

  <PackageReference Include="IdentityModel" Version="6.0.0" />
  <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.3" />

Program.cs

將 Program.cs 的代碼修改爲;

using Microsoft.AspNetCore.Authentication.Cookies;
using System.IdentityModel.Tokens.Jwt;

namespace Dotnet.WebApi.CodeClient
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddControllersWithViews();

            //去除映射,保留Jwt原有的Claim名稱
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            builder.Services.AddAuthentication(options =>{
                    //使用Cookies 
                    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    //使用OpenID Connect 
                    options.DefaultChallengeScheme = "oidc";
                })
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddOpenIdConnect("oidc", options =>
                {
                    options.SignInScheme = "Cookies";
                    //客戶端ID
                    options.ClientId = "WebMvcCodeClient";
                    //客戶端密鑰
                    options.ClientSecret = "WebMvcCodeClient00000001";
                    //IdentityServer4認證服務器地址
                    options.Authority = "https://localhost:6001";
                    //響應授權碼
                    options.ResponseType = "code";
                    //允許Token保存的Cookies中
                    options.SaveTokens = true;
                    //權限範圍
                    options.Scope.Clear();
                    options.Scope.Add("openid");
                    options.Scope.Add("profile");
                    //設置允許獲取刷新Token
                    options.Scope.Add("offline_access");
                    //設置訪問的API範圍
                    options.Scope.Add("Customer.Read");
                    //獲取用戶的Claims信息
                    options.GetClaimsFromUserInfoEndpoint = true;
                });

            var app = builder.Build();

            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            //修改端口號
            app.Urls.Add("https://*:6022");

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();
            //Cookie策略
            app.UseCookiePolicy();
            //用戶驗證
            app.UseAuthentication();
            //授權
            app.UseAuthorization();

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

            app.Run();
        }
    }
}

代碼解析:

(1)添加 AddOpenIdConnect 認證:

          builder.Services.AddAuthentication(options =>{
                    //使用Cookies 
                    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    //使用OpenID Connect 
                    options.DefaultChallengeScheme = "oidc";
                })
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddOpenIdConnect("oidc", options =>
                {
                    options.SignInScheme = "Cookies";
                    //客戶端ID
                    options.ClientId = "WebMvcCodeClient";
                    //客戶端密鑰
                    options.ClientSecret = "WebMvcCodeClient00000001";
                    //IdentityServer4認證服務器地址
                    options.Authority = "https://localhost:6001";
                    //響應授權碼
                    options.ResponseType = "code";
                    //允許Token保存的Cookies中
                    options.SaveTokens = true;
                    //權限範圍
                    options.Scope.Clear();
                    options.Scope.Add("openid");
                    options.Scope.Add("profile");
                    //設置允許獲取刷新Token
                    options.Scope.Add("offline_access");
                    //設置訪問的API範圍
                    options.Scope.Add("Customer.Read");
                    //獲取用戶的Claims信息
                    options.GetClaimsFromUserInfoEndpoint = true;
                });

代碼分析:

  • AddAuthentication:添加身份認證服務

  • options.DefaultScheme=Cookies:我們使用cookie記錄本地登錄用戶

  • options.DefaultChallengeScheme="oidc":需要用戶登錄,將使用OpenID Connect協議,定義質詢(Challenge)方案:質詢(Challenge)就是當發現用戶沒有登錄,使用什麼方案進行認證

  • AddCookie:添加cookies的處理器

  • AddOpenIdConnect:配置執行OpenID Connect協議的處理器相關參數

  • options.Authority:標識所信賴的token服務地址

  • options.ClientId和options.ClientSecret:標識MVC客戶端

  • options.SaveTokens:保存從IdentityServer獲取的token至cookie,ture標識ASP.NETCore將會自動存儲身份認證session的access和refresh token

  • AddOpenIdConnect("oidc", options =>{...}): 添加名爲:“oidc” 的認證方案

  • AddOpenIdConnect()方法已經幫我們處理了授權碼模式的流程中的如下步驟:
    “客戶端再從這個回調的URL中解析到得到code(授權碼),再通過code(授權碼)向服務器發送請求獲取 access_token”,
    故我們不必自己去做這些操作,直接在控制器(Controller)中調用如下方法來獲取 AccessToken:

            //獲取accessToken
            var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);

(2) 修改端口號

       //修改端口號
       app.Urls.Add("https://*:6022");

設置客戶端地址爲:https://localhost:6022

調用受保護的 API 資源

先增文件Controllers/HomeController.cs,修改代碼爲:

using Dotnet.WebApi.CodeClient.Models;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Diagnostics;

namespace Dotnet.WebApi.CodeClient.Controllers
{
    [Authorize]
    public class HomeController : Controller
    {
        ......
        /// <summary>
        /// 獲取API資源。
        /// </summary>
        /// <returns></returns>
        public async Task<IActionResult> ApiData()
        {
            //獲取accessToken
            var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
            //請求API資源
            var httpClient = new HttpClient();
            //將獲取到的AccessToken以Bearer的方案設置在請求頭中
            httpClient.SetBearerToken(accessToken);
            //向API資源服務器請求受保護的API
            var data = await httpClient.GetAsync("https://localhost:6011/api/customer/getlist");
            if (data.IsSuccessStatusCode)
            {
                var r = await data.Content.ReadAsStringAsync();
                ViewBag.ApiData = r;
            }
            else
            {
                ViewBag.ApiData = "獲取API數據失敗。";
            }
            return View();
        }
        ......
    }
}

代碼解析:
(1)在控制器上加入特性:[Authorize],要求用戶必須登錄。
(1)獲取AccessToken:

   var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);

依賴包 Microsoft.AspNetCore.Authentication.OpenIdConnectAddOpenIdConnect();方法,已經幫我們處理了授權碼模式的流程中的如下步驟:
“客戶端再從這個回調的URL中解析到得到code(授權碼),再通過code(授權碼)向服務器發送請求獲取 access_token”,
故我們不必自己去做這些操作,直接在控制器(Controller)中調用如下方法來獲取 AccessToken。

(2)創建 HttpClient 對象,設置 AccessToken,訪問受保護的Api資源:

            //將獲取到的AccessToken以Bearer的方案設置在請求頭中
            httpClient.SetBearerToken(accessToken);
            //向API資源服務器請求受保護的API
            var data = await httpClient.GetAsync("https://localhost:6011/api/customer/getlist");

退出登錄

對於像IdentityServer這樣的身份認證服務,清除本地應用程序cookie是不夠的。還需要往返於IdentityServer以清除中央單點登錄的session。
在控制器中增加退出操作代碼:

public IActionResult Logout()
{
    return SignOut("Cookies", "oidc");
}

添加視圖

Views/Home/Index.cshtml

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<div style="margin:20px;">
    <a href="/home/userinfo">用戶信息</a>
    <a href="/home/apidata">調用API</a>
</div>

Views/Home/ApiData.cshtml

@ViewBag.ApiData

@using Microsoft.AspNetCore.Authentication
@{
    ViewData["Title"] = "用戶信息";
}

<h1>@ViewData["Title"]</h1>
<h2>身份聲明</h2>
<table class="table table-bordered">
    <tr>
        <td>類型</td>
        <td>值</td>
    </tr>
    @foreach (var c in User.Claims)
    {
        <tr>
            <td>@c.Type</td>
            <td>@c.Value</td>
        </tr>
    }
</table>
<h2>屬性</h2>
<table class="table table-bordered">
    <tr>
        <td>類型</td>
        <td>值</td>
    </tr>
    @foreach (var p in (await Context.AuthenticateAsync()).Properties.Items)
    {
        <tr>
            <td>@p.Key</td>
            <td>@p.Value</td>
        </tr>
    }
</table>

Views/Home/UserInfo.cshtml

@using Microsoft.AspNetCore.Authentication
@{
    ViewData["Title"] = "用戶信息";
}

<h1>@ViewData["Title"]</h1>
<h2>身份聲明</h2>
<table class="table table-bordered">
    <tr>
        <td>類型</td>
        <td>值</td>
    </tr>
    @foreach (var c in User.Claims)
    {
        <tr>
            <td>@c.Type</td>
            <td>@c.Value</td>
        </tr>
    }
</table>
<h2>屬性</h2>
<table class="table table-bordered">
    <tr>
        <td>類型</td>
        <td>值</td>
    </tr>
    @foreach (var p in (await Context.AuthenticateAsync()).Properties.Items)
    {
        <tr>
            <td>@p.Key</td>
            <td>@p.Value</td>
        </tr>
    }
</table>

運行結果

訪問客戶端主頁:https://localhost:6022/
由於在HomeController控制器上加入特性:[Authorize],要求用戶必須登錄,所以會自動跳轉到 IdentityServer4 認證服務器的登錄頁面:

此時的 Url 爲:

https://localhost:6001/Account/Login?ReturnUrl=/connect/authorize/callback?client_id=WebMvcCodeClient&redirect_uri=https://localhost:6022/signin-oidc&response_type=code&scope=openid profile offline_access Customer.Read&code_challenge=YLp1AtbYBkj0v0CbLIG6c5iyf_WfpIOLhfoTaBt07yI&code_challenge_method=S256&response_mode=form_post&nonce=638139540542258598.OWMyZjdlNjgtZDUwOC00NmRjLThhNDEtMmNmN2Y1NjU5ZGEwYTk2NDM3NzQtMWI0Zi00MTE1LWEzMDUtYWNjYzEwNWJmOTRj&state=CfDJ8POHoLGk1JtAslbZG_LqxcfmogxZhhCjTLOAIazSeUfDD_bCaNkvWxAIYi1jYhqfLSF9XLYFeA_rF3OmQ2rsBkhUqpAzs9xI8fFPFoMRwxhvAvqTPKQfziUOTk9lQiqwIbHMeiRRd0ArLOBl0OReoCYpfNce-f7hMvPeJ2sHDmD91SsgaUhqSFe1zKwrlIIz_-bvWe7Mezobk5b_UrOjhPMjbD3xVOZuatVZSsZsy3Pov-4M5WesX_xVUM9-TL9RUQ3wkcM1s9JHSGn4HGh_Uwj3cP5Y7Ri7vdG6ijAyyS0_HDIhqdHjHbN6Ri3hxFe-aZ3rVJdQ8PzyJV6SxnhHYdZrhW0l5FHSlgRKr5h_GSpMiOEcxCELMSTrZDpLe6tYtg&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.15.1.0

這包含了 OAuth2.0 協議的授權碼模式規定的相關參數,比如:

  • client_id=WebMvcCodeClient
  • redirect_uri=https://localhost:6022/signin-oid
  • response_type=code
  • scope=openid profile offline_access Customer.Read
  • code_challenge=YLp1AtbYBkj0v0CbLIG6
  • code_challenge_method=S256
  • response_mode=form_post
  • nonce=638139540542258598......
  • state=CfDJ8POHoL...

輸入用戶名和密碼,點擊登錄,然後跳轉到授權頁面:

此時的 Url 爲:

https://localhost:6001/consent?returnUrl=/connect/authorize/callback?client_id=WebMvcCodeClient&redirect_uri=https%3A%2F%2Flocalhost%3A6022%2Fsignin-oidc&response_type=code&scope=openid%20profile%20offline_access%20Customer.Read&code_challenge=YLp1AtbYBkj0v0CbLIG6c5iyf_WfpIOLhfoTaBt07yI&code_challenge_method=S256&response_mode=form_post&nonce=638139540542258598.OWMyZjdlNjgtZDUwOC00NmRjLThhNDEtMmNmN2Y1NjU5ZGEwYTk2NDM3NzQtMWI0Zi00MTE1LWEzMDUtYWNjYzEwNWJmOTRj&state=CfDJ8POHoLGk1JtAslbZG_LqxcfmogxZhhCjTLOAIazSeUfDD_bCaNkvWxAIYi1jYhqfLSF9XLYFeA_rF3OmQ2rsBkhUqpAzs9xI8fFPFoMRwxhvAvqTPKQfziUOTk9lQiqwIbHMeiRRd0ArLOBl0OReoCYpfNce-f7hMvPeJ2sHDmD91SsgaUhqSFe1zKwrlIIz_-bvWe7Mezobk5b_UrOjhPMjbD3xVOZuatVZSsZsy3Pov-4M5WesX_xVUM9-TL9RUQ3wkcM1s9JHSGn4HGh_Uwj3cP5Y7Ri7vdG6ijAyyS0_HDIhqdHjHbN6Ri3hxFe-aZ3rVJdQ8PzyJV6SxnhHYdZrhW0l5FHSlgRKr5h_GSpMiOEcxCELMSTrZDpLe6tYtg&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.15.1.0

點擊【Yes, Allow】進行授權。

授權成功後,返回客戶端頁面,

從授權成功後,返回客戶端頁面這期間,其實還經歷兩個步驟,
第一步:IdentityServer4 認證服務器回調 https://localhost:6022/signin-oidc:
返回的Url爲:

https://localhost:6022/signin-oidc?code=.....&

這個過程太快了,沒法看到鏈接,但可以肯定的是,返回的Url中包含了 OAuth2.0 協議的相關參數,其中就包括名爲:code的授權碼的參數。

第二步:使用code(授權碼)向 IdentityServer認證服務器發送請求獲取AccessToken.

以上這兩個步驟在調用.AddOpenIdConnect()方法後,相關中間件已經幫我們處理了,包括:

  • 獲取accessToken
  • 爲認證方案 “oidc”進行登錄
  • 保存 accessToken 到 Cookie 中

,故我們不必自己去做這些操作,直接在控制器(Controller)中調用如下方法來獲取 AccessToken:

   //獲取accessToken
   var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);

最後返回到主頁,

點擊【調用API】按鈕,我們可以看到通過 AccessToken 訪問資源服務受保護的數據:

點擊【用戶信息】按鈕,我們可以看到用戶信息:

其中
access_token 爲:

eyJhbGciOiJSUzI1NiIsImtpZCI6IkJBRUUyOEI1NkFDNTFFMjI0RTgxQjE0OTI2RTU5REEwIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NzgzNTkyMjgsImV4cCI6MTY3ODM2MjgyOCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwMSIsImF1ZCI6IkN1c3RvbWVyQVBJUmVzb3VyY2UiLCJjbGllbnRfaWQiOiJXZWJNdmNDb2RlQ2xpZW50Iiwic3ViIjoiMDAwMDMiLCJhdXRoX3RpbWUiOjE2NzgzNTkxODcsImlkcCI6ImxvY2FsIiwianRpIjoiNEUyQjc0REIzMDRDM0EzQUQ1RDRCQzc1MEQxRjNGNkQiLCJzaWQiOiJGMEIwRDQyNkM0NTI3Qzc4QkJFODBCMDhFQTg2RkFGRSIsImlhdCI6MTY3ODM1OTIyOCwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsIkN1c3RvbWVyLlJlYWQiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsicHdkIl19.fmeaQgJ14WCRkgE5sZy6X_K3XkavVRfyqVwyK--

id_token 爲:

eyJhbGciOiJSUzI1NiIsImtpZCI6IkJBRUUyOEI1NkFDNTFFMjI0RTgxQjE0OTI2RTU5REEwIiwidHlwIjoiSldUIn0.eyJuYmYiOjE2NzgzNTkyMjgsImV4cCI6MTY3ODM1OTUyOCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwMSIsImF1ZCI6IldlYk12Y0NvZGVDbGllbnQiLCJub25jZSI6IjYzODEzOTU1OTc2NTM5Mzc2Ny5ZekU1TlRBMU5tWXRNbU5rTkMwMFlXSTFMV0l3WmpFdFltTTFPR0kyTmpVNFl6STBOVFF5WmpZMk0yUXROakUwT1MwME5tWTRMVGsyTmpndFltUmxNR1V6TVRWa01EQTMiLCJpYXQiOjE2NzgzNTkyMjgsImF0X2hhc2giOiJUaFVlb25EZFVnNTNLWlU5SEdjSE9BIiwic19oYXNoIjoiZHZPNEZXZko5SjJLRjMzckRnYlJsQSIsInNpZCI6IkYwQjBENDI2QzQ1MjdDNzhCQkU4MEIwOEVBODZGQUZFIiwic3ViIjoiMDAwMDMiLCJhdXRoX3RpbWUiOjE2NzgzNTkxODcsImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.D5dgKxvqXhdnoAIrrQgVbX8B8fSMxdgQCQlXToYMrYEanO2dPA2MPoOrUGrgJXiCcNUZ1Ot9eU19GqLx86ZM-pXqpf_dbDOIPTeqPi07s9Rkjc7Ez7vRY7XiXG93Dn602Egh1clwFwaw9D20YIXGP8cbduDsT1qqk9dp8flaw1bKH2FXuUr-YdOuw9FtapSKk9b1eaD9dPAAyG7dZPIzRrMoVdYITzM0ZZiYTnTiaTTdZO8Xool9l7ByoIz7nHedsBht1bD0hIqYckMNsadEeVZ6tpzxJAGWYaf0gl5mlvlkxrYSerGoLdHLxJZcN6263DBq2T70wdelZst4Vycelg

參考資料

【One by One系列】IdentityServer4(四)授權碼流程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章