(十)React Ant Design Pro + .Net5 WebApi:後端環境搭建-IdentityServer4(二)授權模式

冬先生發表於2022-04-01

一、前言

先交代一下整個Demo專案結構:

  • 一個認證服務(埠5000)IdentityServer4.Authentication
  • 五個授權模式(兩個控制檯程式,三個MVC專案埠5001)資料夾GrantClient
  • 兩個資源服務(WebApi:UserApiResource埠8000,ProductApiResource埠9000)資料夾ApiResource

二、準備認證服務 + 資源服務

1、認證服務

(1)新建一個MVC專案,安裝 IdentityServer4 ,註冊五種授權模式客戶端,程式碼如下

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.AddControllersWithViews();

        services.AddIdentityServer()
        .AddDeveloperSigningCredential()                            //臨時證照
        .AddInMemoryClients(InMemoryConfig.GetClients())            //客戶端模式,InMemory記憶體資料
        .AddInMemoryApiScopes(InMemoryConfig.GetApiScopes())        //作用域
        .AddInMemoryApiResources(InMemoryConfig.GetApiResources())  //資源
        .AddTestUsers(InMemoryConfig.GetTestUser())                 //使用者
        .AddInMemoryIdentityResources(InMemoryConfig.IdentityResources);
    }

    // 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();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseIdentityServer(); //使用IdentityServer4

        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}
public class InMemoryConfig
{
    public static IEnumerable<IdentityResource> IdentityResources =>
    new IdentityResource[]
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        //new IdentityResources.Email(),
        //new IdentityResources.Address(),
        //new IdentityResources.Phone()
    };
    /// <summary>
    /// ApiResource 資源列表
    /// </summary>
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new[]
        {
            new ApiResource("UserApiResource", "獲取使用者資訊API")
            {
                Scopes={ "UserScope" }
            },
            new ApiResource("ProductApiResource", "獲取商品資訊API")
            {
                Scopes={ "ProductScope" }
            }
        };
    }
    /// <summary>
    /// ApiScopes 作用域
    /// </summary>
    public static IEnumerable<ApiScope> GetApiScopes()
    {
        return new ApiScope[]
        {
            new ApiScope("UserScope"),
            new ApiScope("ProductScope")
        };
    }
    /// <summary>
    /// Client 客戶端
    /// </summary>
    public static IEnumerable<Client> GetClients()
    {
        return new[]
        {
            //客戶端模式
            new Client
            {
                ClientId = "ClientCredentials",
                ClientName = "ClientCredentials",
                ClientSecrets = new [] { new Secret("ClientCredentials".Sha256()) },
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                AllowedScopes = new [] { "UserScope" }
            },
            //密碼模式
            new Client
            {
                ClientId = "ResourceOwnerPasswordCredentials",
                ClientName = "ResourceOwnerPasswordCredentials",
                ClientSecrets = new [] { new Secret("ResourceOwnerPasswordCredentials".Sha256()) },
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                AllowedScopes = new []
                {
                    "ProductScope",
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                }
            },
            //簡化模式
            new Client
            {
                ClientId = "Implicit",
                ClientName = "Implicit",
                AllowedGrantTypes = GrantTypes.Implicit,
                RedirectUris = { "https://localhost:5001/signin-oidc" },
                PostLogoutRedirectUris = { "https://localhost:5001/signout-callback-oidc" },
                RequireConsent = true,
                AllowedScopes = new []{
                    "UserScope",
                    "ProductScope",
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                }
            },
            //授權碼模式
            new Client
            {
                ClientId = "AuthorizationCode",
                ClientName = "AuthorizationCode",
                ClientSecrets = new [] { new Secret("AuthorizationCode".Sha256()) },
                AllowedGrantTypes = GrantTypes.Code,
                RedirectUris = { "https://localhost:5001/signin-oidc" },
                PostLogoutRedirectUris = { "https://localhost:5001/signout-callback-oidc" },
                RequireConsent = true,
                AllowedScopes = new []{
                    "UserScope",
                    "ProductScope",
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                }
            },
            //混合模式
            new Client
            {
                ClientId = "Hybrid",
                ClientName = "Hybrid",
                ClientSecrets = new [] { new Secret("Hybrid".Sha256()) },
                AllowedGrantTypes = GrantTypes.Hybrid,
                RedirectUris = { "https://localhost:5001/signin-oidc" },
                PostLogoutRedirectUris = { "https://localhost:5001/signout-callback-oidc" },
                RequireConsent = true,
                RequirePkce = false,
                AllowedScopes = new []{
                    "UserScope",
                    "ProductScope",
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    //IdentityServerConstants.StandardScopes.Email,
                    //IdentityServerConstants.StandardScopes.Address,
                    //IdentityServerConstants.StandardScopes.Phone
                }
            },
        };
    }
    public static List<TestUser> GetTestUser()
    {
        return new List<TestUser>(){
            new TestUser
            {
                SubjectId = "1",
                Username = "WinterSir",
                Password = "WinterSir",
                Claims =
                {
                     new Claim(JwtClaimTypes.Name,"WinterSir"),
                     new Claim(JwtClaimTypes.GivenName,"WinterSir"),
                     new Claim(JwtClaimTypes.FamilyName,"WinterSir-FamilyName"),
                     new Claim(JwtClaimTypes.Email,"641187567@qq.com"),
                     new Claim(JwtClaimTypes.EmailVerified,"true", ClaimValueTypes.Boolean),
                     new Claim(JwtClaimTypes.WebSite,"http://WinterSir.com"),
                     new Claim(JwtClaimTypes.Address,@" [ 'street_address': 'Chang Ping', 'locality': 'BeiJing' ,'postal_code’: 102206,'country': 'China'}",
                     IdentityServerConstants.ClaimValueTypes.Json)
                }
            }
        };
    }
}

(2)cmddotnet new is4ui安裝Quickstart UI模板,刪除原來 Controllers 中 HomeController 防止衝突,設定5000埠啟動

2、資源服務

新建兩個WebApi專案,安裝IdentityServer4.AccessTokenValidation,分別修改Startup、Controller,設定8000、9000埠啟動

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();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "ProductApiResource", Version = "v1" });
        });

        //整合埠為5000的認證服務
        services.AddAuthentication("Bearer")
          .AddIdentityServerAuthentication(options =>
          {
              options.Authority = "https://localhost:5000";
              options.ApiName = "ProductApiResource";
          });
    }

    // 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.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ProductApiResource v1"));
        }

        app.UseRouting();

        app.UseAuthentication();//鑑權

        app.UseAuthorization();//授權

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

三、授權模式

1、客戶端授權模式

客戶端模式(Client Credentials)指客戶端以自己的名義,而不是以使用者的名義,向"認證服務"進行認證。如果是提前約束好的客戶端,直接給你頒發令牌 token

安裝IdentityModel

class Program
{
    /// <summary>
    /// 客戶端模式(Client Credentials)
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {
        Console.WriteLine("***************** 客戶端模式(Client Credentials)*****************");
        var client = new HttpClient();
        var disco = client.GetDiscoveryDocumentAsync("https://localhost:5000/").Result;
        if (disco.IsError)
        {
            Console.WriteLine(disco.Error);
            return;
        }
        var tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
        {
            Address = disco.TokenEndpoint,
            ClientId = "ClientCredentials",
            ClientSecret = "ClientCredentials",
            Scope = "UserScope"
        }).Result;

        if (tokenResponse.IsError)
        {
            Console.WriteLine(tokenResponse.Error);
            return;
        }

        Console.WriteLine("\nToken: " + tokenResponse.AccessToken);

        var apiClient = new HttpClient();
        apiClient.SetBearerToken(tokenResponse.AccessToken);
        var response = apiClient.GetAsync("https://localhost:8000/User/Get").Result;
        if (!response.IsSuccessStatusCode)
        {
            Console.WriteLine(response.StatusCode);
        }
        else
        {
            var content = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine("\n結果: " + content);
        }

        Console.ReadLine();
    }
}

2、密碼模式

密碼模式(Resource Owner Password Credentials)中客戶端使用使用者提供的使用者名稱和密碼,向"認證服務"進行認證,有較高風險,通常只有在其他授權模式無法執行的情況下,才能考慮使用這種模式。相較於客戶端多了一個使用者角色。

安裝IdentityModel

static void Main(string[] args)
{
    Console.WriteLine("***************** 密碼模式(Resource Owner Password credentials)***************** ");
    var client = new HttpClient();
    var disco = client.GetDiscoveryDocumentAsync("https://localhost:5000/").Result;
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        return;
    }
    var tokenResponse = client.RequestPasswordTokenAsync(new PasswordTokenRequest()
    {
        Address = disco.TokenEndpoint,
        ClientId = "ResourceOwnerPasswordCredentials",
        ClientSecret = "ResourceOwnerPasswordCredentials",
        UserName = "WinterSir",
        Password = "WinterSir",
        Scope = "ProductScope",
    }).Result;

    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return;
    }

    Console.WriteLine("\nToken: " + tokenResponse.AccessToken);

    var apiClient = new HttpClient();
    apiClient.SetBearerToken(tokenResponse.AccessToken);
    var response = apiClient.GetAsync("https://localhost:9000/Product/Get").Result;
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
    }
    else
    {
        var content = response.Content.ReadAsStringAsync().Result;
        Console.WriteLine("\n結果: " + content);
    }

    Console.ReadLine();
}

3、簡化模式

簡化模式(Implicit)比授權碼模式少了code環節,所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認證,該模式是很不安全的,且不支援refresh token,適用於 Web 安全要求不高的場景,設定較短時效的 token。

(1)安裝IdentityServer4.AccessTokenValidation、Microsoft.AspNetCore.Authentication.OpenIdConnect,修改Startup

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)
    {
        //關閉Jwt對映
        JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
        //註冊授權
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.Authority = "https://localhost:5000";       //認證服務
            options.RequireHttpsMetadata = true;                //必須使用Https,否則使用者無法登入
            options.ClientId = "Implicit";
            options.ClientSecret = "Implicit";
            options.SaveTokens = true; //表示Token要儲存
        });

        services.AddControllersWithViews().AddRazorRuntimeCompilation();
    }

    // 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();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();

        app.UseAuthorization();

        app.UseHttpsRedirection();

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

(2)_Layout.cshtml新增 登出按鈕

<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </li>
    </ul>
    <a class="nav-link text-dark float-right" asp-area="" asp-controller="Home" asp-action="Logout">Logout</a>
</div>

(3)HomeController新增對應功能,需要認證的方法加上特性[Authorize]

[Authorize]
public IActionResult Privacy()
{
    return View();
}

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

(4)修改Privacy.cshtml

@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

@using Microsoft.AspNetCore.Authentication

<h2>Claims</h2>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<h2>Properties</h2>

<dl>
    @foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items)
    {
        <dt>@prop.Key</dt>
        <dd>@prop.Value</dd>
    }
</dl>

(5)效果圖

4、授權碼模式

授權碼模式(Authorization Code)不同於簡化模式直接返回token,而是先返回一個授權碼,再用授權碼去請求token,然後攜帶訪問Api資源。授權碼模式是功能最完整、流程最嚴密的授權模式。

(1)安裝IdentityServer4.AccessTokenValidation、Microsoft.AspNetCore.Authentication.OpenIdConnect,修改Startup

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)
    {
        //關閉Jwt對映
        JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
        //註冊授權
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.Authority = "https://localhost:5000";       //認證服務
            options.RequireHttpsMetadata = true;                //必須使用Https,否則使用者無法登入
            options.ClientId = "AuthorizationCode";
            options.ClientSecret = "AuthorizationCode";
            options.ResponseType = "code";
            options.Scope.Clear();
            options.Scope.Add("UserScope");
            options.Scope.Add("ProductScope");
            options.Scope.Add(OidcConstants.StandardScopes.OpenId);
            options.Scope.Add(OidcConstants.StandardScopes.Profile);
            //options.Scope.Add(OidcConstants.StandardScopes.Email);
            //options.Scope.Add(OidcConstants.StandardScopes.Phone);
            //options.Scope.Add(OidcConstants.StandardScopes.Address);
            options.SaveTokens = true; //表示Token要儲存
        });

        services.AddControllersWithViews().AddRazorRuntimeCompilation();
        services.AddControllers().AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
        });
    }

    // 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();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();

        app.UseAuthorization();

        app.UseHttpsRedirection();

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

(2)_Layout.cshtml新增 獲取使用者按鈕、登出按鈕

<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="User">UserApi</a>
        </li>
    </ul>
    <a class="nav-link text-dark float-right" asp-area="" asp-controller="Home" asp-action="Logout">Logout</a>
</div>

(3)HomeController新增對應功能,需要認證的方法加上特性[Authorize]

[Authorize]
public IActionResult Privacy()
{
    return View();
}

[Authorize]
public async Task<IActionResult> User()
{
    var client = new HttpClient();
    var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
    if (string.IsNullOrEmpty(accessToken))
    {
        return Json(new { msg = "accesstoken 獲取失敗" });
    }
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    var httpResponse = await client.GetAsync("https://localhost:8000/User/Get");
    var result = await httpResponse.Content.ReadAsStringAsync();
    if (!httpResponse.IsSuccessStatusCode)
    {
        ViewBag.Result = new { msg = "請求 User/Get 失敗", error = result };
    }
    ViewBag.Result = new { msg = "成功", data = result };
    return View();
}

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

(4)修改Privacy.cshtml

@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

@using Microsoft.AspNetCore.Authentication

<h2>Claims</h2>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<h2>Properties</h2>

<dl>
    @foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items)
    {
        <dt>@prop.Key</dt>
        <dd>@prop.Value</dd>
    }
</dl>

(5)效果圖

5、混合模式

混合模式(Hybrid Flow)

它為我們提供了兩全其美的優勢,身份令牌通過瀏覽器傳輸,因此客戶端可以在進行任何更多工作之前對其進行驗證。如果驗證成功,客戶端會通過令牌服務的以獲取訪問令牌

(1)安裝IdentityServer4.AccessTokenValidation、Microsoft.AspNetCore.Authentication.OpenIdConnect,修改Startup

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)
    {
        //關閉Jwt對映
        JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
        //註冊授權
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.Authority = "https://localhost:5000";       //認證服務
            options.RequireHttpsMetadata = true;                //必須使用Https,否則使用者無法登入
            options.ClientId = "Hybrid";
            options.ClientSecret = "Hybrid";
            options.ResponseType = "code id_token";
            options.Scope.Clear();
            options.Scope.Add("UserScope");
            options.Scope.Add("ProductScope");
            options.Scope.Add(OidcConstants.StandardScopes.OpenId);
            options.Scope.Add(OidcConstants.StandardScopes.Profile);
            //options.Scope.Add(OidcConstants.StandardScopes.Email);
            //options.Scope.Add(OidcConstants.StandardScopes.Phone);
            //options.Scope.Add(OidcConstants.StandardScopes.Address);
            //options.Scope.Add(OidcConstants.StandardScopes.0fflineAccess);//獲取到重新整理Token
            options.SaveTokens = true; //表示Token要儲存
        });

        services.AddControllersWithViews().AddRazorRuntimeCompilation();
    }

    // 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();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();

        app.UseAuthorization();

        app.UseHttpsRedirection();

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

(2)_Layout.cshtml新增 獲取產品、登出按鈕

<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Product">ProductApi</a>
        </li>
    </ul>
    <a class="nav-link text-dark float-right" asp-area="" asp-controller="Home" asp-action="Logout">Logout</a>
</div>

(3)HomeController新增對應功能,需要認證的方法加上特性[Authorize]

[Authorize]
public IActionResult Privacy()
{
    return View();
}
[Authorize]
public async Task<IActionResult> Product()
{
    var client = new HttpClient();
    var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
    if (string.IsNullOrEmpty(accessToken))
    {
        return Json(new { msg = "accesstoken 獲取失敗" });
    }
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    var httpResponse = await client.GetAsync("https://localhost:9000/Product/Get");
    var result = await httpResponse.Content.ReadAsStringAsync();
    if (!httpResponse.IsSuccessStatusCode)
    {
        ViewBag.Result = new { msg = "請求 User/Get 失敗", error = result };
    }
    ViewBag.Result = new { msg = "成功", data = result };
    return View();
}

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

(4)修改Privacy.cshtml

@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

@using Microsoft.AspNetCore.Authentication

<h2>Claims</h2>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<h2>Properties</h2>

<dl>
    @foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items)
    {
        <dt>@prop.Key</dt>
        <dd>@prop.Value</dd>
    }
</dl>

(5)效果圖

四、問題踩坑

1、Https

Demo全部用的Https,Mvc客戶端配置RequireHttpsMetadata = true如果使用http遇到認證服無法務登入問題,可參考以下地址
https://www.cnblogs.com/i3yuan/p/14033016.html#autoid-20-0-0

2、ResponseType

授權碼模式、混合模式需要修改客戶端配置ResponseType,ResponseType = "code" 、 ResponseType = "code id_token"

3、RequirePkce

混合模式需要修改對應服務端註冊客戶端時配置RequirePkce = false,這樣不需要客戶端提供code challeng

4、其他Error

出現錯誤大概率是客戶端、服務端配置項問題,仔細對比一下就OK了

五、前人栽樹,後人乘涼

https://www.cnblogs.com/i3yuan/category/1777690.html

六、程式碼已上傳

https://github.com/WinterSir/IdentityServer4.GrantTypesDemo

相關文章