認證授權:IdentityServer4 - 各種授權模式應用
前言:
前面介紹了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)在InMemoryConfig中GetClients方法中新增客戶端:
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)在InMemoryConfig中GetClients方法中新增客戶端:
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)在InMemoryConfig中GetClients方法中新增客戶端:
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)在InMemoryConfig中GetClients方法中新增客戶端:
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)在InMemoryConfig中GetClients方法中新增客戶端:
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
&n