本文,主要用來記錄IdentityServer4的簡單使用。
一. IdentityServer的預備知識
要學習IdentityServer,需要了解下基於Token的驗證體系,其中涉及到Token, OAuth&OpenID,JWT,協議規範等。
如圖過程,
二. IdentityServer簡單介紹
IdentityServer4 是一個基於OpenID Connect和OAuth 2.0的針對ASP.NET Core 2.0的框架,以中介軟體的形式存在。
通常你可以構建(或重新使用)包含登入和登出頁面的應用程式,IdentityServer中介軟體會向其新增必要的協議頭,以便客戶端應用程式可以使用這些標準協議與其對話。
我們可以用IdentityServer來做什麼?
- 身份驗證服務:官方認證的OpenID Connect實現
- 單點登入/登出(SSO)
- 訪問受控的API : 為不同的客戶提供訪問API的令牌,比如:MVC網站、SPA、Mobile APP等
- ...等等
三.簡單專案示例
先列出目錄結構,以及建立順序,來方便閱讀
IdentityServerDemo --> APIService1和APIService2 --> MVCClient
其中,處MVCClient是asp.net core web mvc專案外,其他都是asp.net core web api 專案
建立名為IdentityServerDemo的認證服務
1. 建立一個asp.net core web api專案:IdentityServerDemo。
注意,不要設定HTTPS,否則後面使用postman測試時,會no response
2. 新增InMemoryConfiguration
public class InMemoryConfiguration { public static IConfiguration Configuration { get; set; } /// <summary> /// Define which APIs will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("clientservice", "CAS Client Service"), new ApiResource("productservice", "CAS Product Service"), new ApiResource("agentservice", "CAS Agent Service") }; } /// <summary> /// Define which Apps will use thie IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId = "client.api.service", ClientSecrets = new [] { new Secret("clientsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "clientservice" } }, new Client { ClientId = "product.api.service", ClientSecrets = new [] { new Secret("productsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "clientservice", "productservice" } }, new Client { ClientId = "agent.api.service", ClientSecrets = new [] { new Secret("agentsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "agentservice", "clientservice", "productservice" } } }; } /// <summary> /// Define which uses will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<TestUser> GetUsers() { return new[] { new TestUser { SubjectId = "10001", Username = "test1@hotmail.com", Password = "test1password" }, new TestUser { SubjectId = "10002", Username = "test2@hotmail.com", Password = "test2password" }, new TestUser { SubjectId = "10003", Username = "test3@hotmail.com", Password = "test3password" } }; } }
3. 使用nuget管理器,新增IdentityServer4 ,並且修改StartUp.cs
修改StartUp.cs中的Configure方法
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //啟用IdentityServer app.UseIdentityServer(); app.UseMvc(); }
修改StartUp.cs中的ConfigureServices方法
public void ConfigureServices(IServiceCollection services) { //新增IdentityServer services.AddIdentityServer() .AddDeveloperSigningCredential() .AddTestUsers(InMemoryConfiguration.GetUsers().ToList()) .AddInMemoryClients(InMemoryConfiguration.GetClients()) .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources()); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
這個主要是為了把IdentityServer註冊到容器中,需要對其進行配置,而這個配置主要包含三個資訊:
- 哪些api可以使用這個AuthorizationServer
- 哪些client可以使用這個AuthorizationServer
- 哪些User可以被這個AuthorizationServer識別並授權
這裡的AuthorizationServer 指的就是這個專案的服務:用來認證及授權使用的.
這裡是使用基於記憶體的方式。
對於Token簽名需要一對公鑰和私鑰,IdentityServer為開發者提供了一個AddDeveloperSigningCredential()方法,它會幫我們搞定這個事情並且儲存到硬碟。當切換到正式環境,需要使用真正的證照,更換為
public void ConfigureServices(IServiceCollection services) { InMemoryConfiguration.Configuration = this.Configuration; services.AddIdentityServer() .AddDeveloperSigningCredential() .AddTestUsers(InMemoryConfiguration.GetUsers().ToList()) .AddInMemoryClients(InMemoryConfiguration.GetClients()) .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources()); }
此專案,暫時不使用正式的證照了。
4.使用postman獲取token
啟動我們的IdentityServerDemo 專案,
然後使用postman傳送請求
5.引入QuickStartUI
IdentityServer為我們提供了一套UI以使我們能快速的開發具有基本功能的認證/授權介面,下載地址:QuickStartUI
把QuickStartUI引入到我們的專案中,目錄結構如下:
5.修改StartUp.cs
修改Configure方法
新增靜態檔案中介軟體
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //啟用IdentityServer app.UseIdentityServer(); //for QuickStart-UI 啟用靜態檔案 app.UseStaticFiles(); //app.UseMvc(); app.UseMvcWithDefaultRoute(); //這裡帶有預設的路由 }
6.執行程式
登入
點選here
登出
IdentityServer整合API Service
1. 新增asp.net core web api專案
注意,這裡也是使用http方式;
2.在nuget中安裝IdentityServer4.AccessTokenValidation
3.修改StartUp.cs檔案
修改configureServices方法
public void ConfigureServices(IServiceCollection services) { //IdentityServer services.AddMvcCore().AddAuthorization().AddJsonFormatters(); services.AddAuthentication(Configuration["Identity:Scheme"]) .AddIdentityServerAuthentication(options => { options.RequireHttpsMetadata = false; //是否需要https options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}"; //IdentityServer授權路徑 options.ApiName = Configuration["Service:Name"]; //需要授權的服務名稱 }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
修改Configure方法
在UseMvc()之前啟用Authentication中介軟體
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //啟用Authentication中介軟體 app.UseAuthentication(); app.UseMvc(); }
修改appsettings.json檔案
{ "Service": { "Name": "clientservice", //本服務的名稱 "Port": "53064", //本服務的埠號,根據自己服務啟動時的埠號進行更改 "DocName": "clientservice", "Version": "v1", "Title": "CAS Client Service API", "Description": "CAS Client Service API provide some API to help you get client information from CAS", "Contact": { "Name": "CAS 2.0 Team", "Email": "EdisonZhou@manulife.com" }, "XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml" }, "Identity": { //去請求授權的Identity服務,這裡即IdentityServerDemo的服務啟動時的地址 "IP": "localhost", "Port": "49363", //IdentityServerDemo專案啟動時的埠號,根據實際情況修改 "Scheme": "Bearer" } }
上面是APIService1的新增,對應的服務名稱是clientservice;
APIService2與之類似,只是把appsettings.json中的clientservice改為productservice.
4. 在APIService1和APIService2的Controller新增[Authorize]特性
[Authorize] [Route("api/[controller]")] public class ValuesController : Controller { ...... }
5. 測試
注意,這裡模擬的是clientservice服務(即APIService1)去認證伺服器請求token的過程,所以請求到token,也應該在獲取clientservice相關授權的時候攜帶這個token.
如果在請求productservice的授權服務中,使用clientservice的token則會顯示未授權
過程總結:
- 首先,在授權服務中,設定需要請求的ApiResource,client,user
- 在postman(相當於client)中,輸入client的相關資訊(client_id,client_serect)去請求token
- 然後就可以根據授權服務中相應client的AllowedScopes設定的範圍來請求服務了。
授權服務中的client設定
IdentityServer整合MVC Web Application
1. 新建一個ASP.NET Core MVC專案:MVCClient
2.為指定方法新增[Authorize]特性
我們為HomeController下的Privacy方法上新增Authorize特性
[Authorize] public IActionResult Privacy() { return View(); }
這個時候,直接訪問Privacy,會報錯
而我們希望的效果是:當使用者第一次點選Privacy,頁面重定向到驗證服務(IdentityServerDemo),當使用者登入驗證授權後,再重定向到該網站。
此後一定時間範圍內的第二次,第三次點選,都不需要再重定向到驗證服務,而是直接讀取儲存的token.
3. 給MVCClient專案新增OpenID Connect Authentication
而這部分主要集中於做Authentication(身份驗證)而非Authorization(授權)
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.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //這部分主要是做身份驗證的(Authentication),而不是授權(Authorization) JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; //oidc => open id connect }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}"; options.RequireHttpsMetadata = false; options.ClientId = "cas.mvc.client.implicit"; options.ResponseType = "id_token token"; //允許返回access token options.SaveTokens = true; }); }
這裡我們使用的是implicit這個flow,它主要用於客戶端應用程式(主要指基於javascript的應用),它允許客戶端程式重定向到驗證服務(IdentityServerDemo),而後帶著token重定向回來。
另外,這裡的ResponseType為”id_token token”,表示既獲取id_token也獲取access_token. 而SaveTokens設定為true,表示會將從驗證服務返回的token持久化到cookie中,這樣就不用每次請求token了。
另在configure方法中,設定Authentication中介軟體:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseAuthentication(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
主要Authentication中介軟體,要再UseMvc之前。
4. 修改app.settings
{ "Service": { "Name": "cas.mvc.client.implicit", //本服務的名稱 "Port": "56458", //服務埠號,根據實際情況調整 "DocName": "cas.mvc.client.implicit", "Version": "v1", "Title": "CAS Client Service API", "Description": "CAS Client Service API provide some API to help you get client information from CAS", "Contact": { "Name": "CAS 2.0 Team", "Email": "EdisonZhou@manulife.com" }, "XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml" }, "Identity": { //去請求授權的Identity服務 "IP": "localhost", "Port": "49363" } }
其中port根據自己此服務啟動後的埠號修改
5.在驗證服務(IdentityServerDemo)中新增MvcClient
修改 InMemoryConfiguration 中的GetClients方法:
public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId = "client.api.service", ClientSecrets = new [] { new Secret("clientsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "clientservice" } }, new Client { ClientId = "product.api.service", ClientSecrets = new [] { new Secret("productsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "clientservice", "productservice" } }, new Client { ClientId = "agent.api.service", ClientSecrets = new [] { new Secret("agentsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "agentservice", "clientservice", "productservice" } }, new Client { ClientId = "cas.mvc.client.implicit", ClientName = "CAS MVC Web App Client", AllowedGrantTypes = GrantTypes.Implicit, RedirectUris = { $"http://localhost:56458/signin-oidc" }, PostLogoutRedirectUris = { $"http://localhost:56458/signout-callback-oidc" }, AllowedScopes = new [] { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "agentservice", "clientservice", "productservice" }, AllowAccessTokensViaBrowser = true // can return access_token to this client }, }; }
這裡ClientId要和MvcClient中設定的一樣。
RedirectUris是指登入成功以後需要重定向的地址(即重定向到MvcClient中的地址),
而PostLogoutRedirectUris是指登出之後需要重定向的地址。
和API Service Client的設定不同的就是AllowedScopes中給它增加了OpenId和Profile,因為我們為MvcClient設定的是oidc而不是bearer模式。
最後為了使用這些OpenID Connect Scopes,需要設定這些Identity Resources。
在 InMemoryConfiguration 中增加GetIdentityResources方法:
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; }
在ConfigureServices方法中修改:
public void ConfigureServices(IServiceCollection services) { //新增IdentityServer services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources()) .AddTestUsers(InMemoryConfiguration.GetUsers().ToList()) .AddInMemoryClients(InMemoryConfiguration.GetClients()) .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources()); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
6. 在MvcClient專案的Privacy 頁面中修改如下:
@{ ViewData["Title"] = "Privacy Policy"; } <h1>@ViewData["Title"]</h1> <p>Use this page to detail your site's privacy policy.</p> @using Microsoft.AspNetCore.Authentication <div> <strong>id_token</strong> <span>@await ViewContext.HttpContext.GetTokenAsync("id_token")</span> </div> <div> <strong>access_token</strong> <span>@await ViewContext.HttpContext.GetTokenAsync("access_token")</span> </div> <dl> @foreach (var claim in User.Claims) { <dt>@claim.Type</dt> <dd>@claim.Value</dd> } </dl>
這裡,我們會把id_token和access_token顯示出來
7. 為了退出方便,暫時在HomeController下增加Logout方法
public async Task Logout() { await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); }
8. 簡單測試
啟動IdentityServerDemo這個驗證服務;
啟動MvcClient這個Mvc Web Application服務;
這裡沒有新增可點選的按鈕,可直接在url中修改路徑來登出
參考網址:
https://www.cnblogs.com/edisonchou/p/identityserver4_foundation_and_quickstart_01.html
另外推薦edisonchou微服務系列,感覺非常棒