認證受權:IdentityServer4 - 各類受權模式應用

前言:

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

受權模式:

 環境準備

 a)調整項目結構以下:html

  

 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項目中添加控制器:IdentityControllerjava

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

 一、客戶端模式

  a)在InMemoryConfigGetClients方法中添加客戶端:git

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中添加以下方法:github

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

   運行該程序結果以下:後端

   

 二、密碼模式

  a)在InMemoryConfigGetClients方法中添加客戶端:api

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中添加以下方法:cookie

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

   運行該程序結果同上:  app

 三、簡化模式

  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

 四、受權碼模式

  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)運行結果以下:同簡化模式運行效果相同

 五、混合模式(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

相關文章
相關標籤/搜索