認證授權:IdentityServer4 - 各種授權模式應用

cwsheng發表於2020-09-13

前言:

 前面介紹了IdentityServer4 的簡單應用,本篇將繼續講解IdentityServer4 的各種授權模式使用示例

授權模式:

 環境準備

 a)調整專案結構如下:

  

 b)調整cz.IdentityServer專案中Statup檔案如下 

public class Startup
  {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            services.Configure<CookiePolicyOptions>(options =>
            {
                options.MinimumSameSitePolicy = SameSiteMode.Strict;
            });

            services.AddIdentityServer()
              .AddDeveloperSigningCredential()
              //api資源
              .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
              //4.0版本需要新增,不然呼叫時提示invalid_scope錯誤
              .AddInMemoryApiScopes(InMemoryConfig.GetApiScopes())
              .AddTestUsers(InMemoryConfig.Users().ToList())
              .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResourceResources())
              .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.UseCookiePolicy();
            app.UseIdentityServer();

            app.UseAuthentication();
            //使用預設UI,必須新增
            app.UseAuthorization();

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

 c)在cz.Api.Order專案中新增控制器:IdentityController

namespace cz.Api.Order.Controllers
{
    [Route("identity")]
    [ApiController]
    [Authorize]
    public class IdentityController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }
}

 1、客戶端模式

  a)在InMemoryConfigGetClients方法中新增客戶端:

new Client
{
    ClientId = "credentials_client", //訪問客戶端Id,必須唯一
    ClientName = "ClientCredentials Client",
    //使用客戶端授權模式,客戶端只需要clientid和secrets就可以訪問對應的api資源。
    AllowedGrantTypes = GrantTypes.ClientCredentials,
    ClientSecrets =
        {
            new Secret("secret".Sha256())
        },
    AllowedScopes = {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "goods"
    },
},     

  b)在cz.ConsoleClient專案中安裝Nuget包:IdentityModel,在Program中新增如下方法:

/// <summary>
/// 客戶端認證模式
/// </summary>
private static void ClientCredentials_Test()
{
    Console.WriteLine("ClientCredentials_Test------------------->");
    var client = new HttpClient();
    var disco = client.GetDiscoveryDocumentAsync("http://localhost:5600/").Result;
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        return;
    }
    //請求token
    var tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
    {
        Address = disco.TokenEndpoint,
        ClientId = "credentials_client",
        ClientSecret = "secret",
        Scope = "goods"
    }).Result;

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

    Console.WriteLine(tokenResponse.Json);
    //呼叫認證api
    var apiClient = new HttpClient();
    apiClient.SetBearerToken(tokenResponse.AccessToken);

    var response = apiClient.GetAsync("http://localhost:5601/identity").Result;
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
    }
    else
    {
        var content = response.Content.ReadAsStringAsync().Result;
        Console.WriteLine(content);
    }
}

   執行該程式結果如下:

   

 2、密碼模式

  a)在InMemoryConfigGetClients方法中新增客戶端:

new Client
{
    ClientId = "password_client",
    ClientName = "Password Client",
    ClientSecrets = new [] { new Secret("secret".Sha256()) },
    //這裡使用的是通過使用者名稱密碼換取token的方式.
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    AllowedScopes = {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "order","goods",
    }
},

  b)cz.ConsoleClient專案繼續在Program中新增如下方法:

/// <summary>
/// 使用者名稱密碼模式
/// </summary>
public static void ResourceOwnerPassword_Test()
{
    Console.WriteLine("ResourceOwnerPassword_Test------------------->");
    // request token
    var client = new HttpClient();
    var disco = client.GetDiscoveryDocumentAsync("http://localhost:5600/").Result;
    var tokenResponse = client.RequestPasswordTokenAsync(new PasswordTokenRequest()
    {
        Address = disco.TokenEndpoint,
        ClientId = "password_client",
        ClientSecret = "secret",
        UserName = "cba",
        Password = "cba",
        Scope = "order goods",
    }).Result;

    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return;
    }
    Console.WriteLine(tokenResponse.Json);
    // call api
    var apiClient = new HttpClient();
    client.SetBearerToken(tokenResponse.AccessToken);
    var response = apiClient.GetAsync("http://localhost:5601/identity").Result;
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
    }
    else
    {
        var content = response.Content.ReadAsStringAsync().Result;
        Console.WriteLine(content);
    }
}

   執行該程式結果同上:  

 3、簡化模式

  a)在InMemoryConfigGetClients方法中新增客戶端:

new Client
{
    ClientId = "implicit_client",
    ClientName = "Implicit Client",
    ClientSecrets = new [] { new Secret("secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Implicit,
    AllowedScopes = {
        "order","goods",
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile
    },
    RedirectUris = { "http://localhost:5021/signin-oidc" },
    PostLogoutRedirectUris = { "http://localhost:5021" },
    //是否顯示授權提示介面
    RequireConsent = true,
},

 

  b)調整在cz.MVCClient中Statup檔案中內容如下:

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.Configure<CookiePolicyOptions>(options =>
        {
          // This lambda determines whether user consent for non-essential cookies is needed for a given request.
          options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.Lax;
        });

        JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

        services.AddControllersWithViews();

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.RequireHttpsMetadata = false;
            options.Authority = "http://localhost:5600";
            options.ClientId = "implicit_client";
            options.ClientSecret = "secret";
        });
    }

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

        app.UseRouting();

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

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

 

  c)在cz.MVCClient中新增Nuget包:IdentityServer4.AccessTokenValidation、Microsoft.AspNetCore.Authentication.OpenIdConnect;在HomeController中新增方法:

[Authorize]
public IActionResult Secure()
{
    ViewData["Message"] = "Secure page.";

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

  d)介面調整:

   在_Layout.cshtml檔案中新增導航按鈕:Secure、Logout   

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Secure">Secure</a>
</li>
@if (User.Identity.IsAuthenticated)
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Logout">Logout</a>
    </li>
}

   新增檢視:Secure.cshtml檔案:

@{
    ViewData["Title"] = "Secure";
}

<h2>@ViewData["Title"]</h2>

<h3>User claims</h3>

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

  e)執行結果如下:

  

  簡化模式還支援在Js客戶端中執行可以檢視官方說明文件:https://identityserver4.readthedocs.io/en/latest/quickstarts/4_javascript_client.html

 4、授權碼模式

  a)在InMemoryConfigGetClients方法中新增客戶端:

new Client
{
    ClientId = "code_client",
    ClientName = "Code Client",
    ClientSecrets = new [] { new Secret("secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Code,
    RedirectUris = { "http://localhost:5021/signin-oidc" },
    PostLogoutRedirectUris = { "http://localhost:5021/signout-callback-oidc" },
  //是否顯示授權提示介面 RequireConsent
= true, AllowedScopes = { "order","goods", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } },

  b)調整在cz.MVCClient中Statup檔案中ConfigureServices方法內容如下:

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

        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.Lax;
        });

        JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

        services.AddControllersWithViews();

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.RequireHttpsMetadata = false;
            options.Authority = "http://localhost:5600";
            options.ClientId = "code_client";
            options.ClientSecret = "secret";
            options.ResponseType = "code";
            options.SaveTokens = true;
            options.Scope.Add("order");
            options.Scope.Add("goods");
            options.GetClaimsFromUserInfoEndpoint = true;
        });
    }

  c)執行結果如下:同簡化模式執行效果相同

 5、混合模式(Hybrid)

a)在InMemoryConfigGetClients方法中新增客戶端:

new Client
{
    ClientId = "hybrid_client",
    ClientName = "Hybrid Client",
    ClientSecrets = new [] { new Secret("secret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Hybrid,
    //是否顯示授權提示介面
    RequireConsent = true,
    AllowedScopes = {
        "order","goods",
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile
    }
}

 

  b)調整在cz.MVCClient中Statup檔案中ConfigureServices方法內容如下:

public void ConfigureServices(IServiceCollection services)
{

    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.Lax;
    });

    JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

    services.AddControllersWithViews();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {
        options.RequireHttpsMetadata = false;
        options.Authority = "http://localhost:5600";
        options.ClientId = "hybrid_client";
        options.ClientSecret = "secret";
        options.ResponseType = "code token id_token";
        options.SaveTokens = true;
        options.ResponseMode = "fragment";
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("order");
        options.Scope.Add("goods");
    });
}

 

總結:

 應用場景總結

  • 客戶端模式(Client Credentials):和使用者無關,應用於應用程式與 API 資源之間的互動場景。
  • 密碼模式:和使用者有關,常用於第三方登入。
  • 簡化模式:可用於前端或無線端。
  • 混合模式:推薦使用,包含 OpenID 認證服務和 OAuth 授權,針對的是後端服務呼叫。

  過程中遇到的坑:

  • Postman呼叫時總是提示:invalid_scope異常;

   解決:在新增IdentityServer服務時:呼叫AddInMemoryApiScopes方法註冊Scope

  • MVC專案登入成功後跳轉時,找不到http://localhost:5020/signin-oidc路徑:

   解決:在Statup檔案中新增services.Configure<CookiePolicyOptions>(options =>{options.CheckConsentNeeded = context => true;options.MinimumSameSitePolicy = SameSiteMode.Lax; });

  • 登入時授權介面展示展示:

   解決:客戶端註冊時,指定屬性RequireConsent= true

 

Git地址:https://github.com/cwsheng/IdentityServer.Demo.git

 

相關文章