寫在前面
1、原始碼(.Net Core 2.2)
git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.git
2、相關章節
2.1、《IdentityServer4 (1) 客戶端授權模式(Client Credentials)》
2.2、《IdentityServer4 (2) 密碼授權(Resource Owner Password)》
2.3、《IdentityServer4 (3) 授權碼模式(Authorization Code)》
2.4、《IdentityServer4 (4) 靜默重新整理(Implicit)》
2.5、《IdentityServer4 (5) 混合模式(Hybrid)》
3、參考資料
IdentityServer4 中文文件 http://www.identityserver.com.cn/
IdentityServer4 英文文件 https://identityserver4.readthedocs.io/en/latest/
OpenID Connect 官網 https://openid.net/connect/
OpenID Connect 中文 https://www.cnblogs.com/linianhui/p/openid-connect-core.html
OpenID Connect和OAuth 2.0對比:https://www.jianshu.com/p/d453076e6433
4、流程圖
1、訪問客戶端受保護的url
2、跳轉到授權伺服器認證
3、使用者填寫認證(賬號密碼)
4、認證成功,選擇授權的scope
5、授權成功(點選同意授權),攜帶授權碼code返回客戶端
6、客戶端通過後臺通訊請求AccessToken 和IdToken,如果設定了 OfflineAccess=true也會返回RefreshToken(可以重新整理AccessToken)
整個訪問流程建議使用fiddler 抓包檢視,更好的瞭解
一、服務端
1、下載一個官方示例
地址:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI
根據自己使用的Core 版本下載對應的,點選 tags 裡找,我下載的是2.5,解壓後如下圖
2、新建一個 web 專案 .NetCore 2.2版本
把官網下載的檔案新增到專案中,Quickstart 我換成了Controllers
3、新增配置類 (IdpConfig.cs)
參看 《IdentityServer4 (1) 客戶端授權模式(Client Credentials)》裡的,直接複製過來就可以了
4、新增客戶端
IdpConfig.cs GetClients()
AllowedScopes 屬性設定的值,必須在GetApiResources() GetApis() 裡提前定義好,並且在 StartUp.cs 裡已經註冊
new Client{ ClientId="mvc client", //客戶端Id ClientName="測試客戶端", //客戶端名稱 隨便寫 AllowedGrantTypes=GrantTypes.Code,//驗證模式 ClientSecrets= { new Secret("mvc secret".Sha256()) //客戶端驗證金鑰 }, // 登陸以後 我們重定向的地址(客戶端地址), // {客戶端地址}/signin-oidc是系統預設的不用改,也可以改,這裡就用預設的 RedirectUris = { "http://localhost:5003/signin-oidc" }, //登出重定向的url PostLogoutRedirectUris = { "http://localhost:5003/signout-callback-oidc" }, //是否允許申請 Refresh Tokens //參考地址 https://identityserver4.readthedocs.io/en/latest/topics/refresh_tokens.html AllowOfflineAccess=true, //將使用者claims 寫人到IdToken,客戶端可以直接訪問 AlwaysIncludeUserClaimsInIdToken=true, //客戶端訪問許可權 AllowedScopes = { "api1", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Email, IdentityServerConstants.StandardScopes.Address, IdentityServerConstants.StandardScopes.Phone, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess } }
5、註冊相關資訊(StartUp.cs)
ConfigureServices() 注意這裡我修改了路徑,如果你使用的是 git 下載下來的不需要修改,我這裡修改是我把 git 下載的改動了
services.AddIdentityServer(options => { //預設的登陸頁面是/account/login options.UserInteraction.LoginUrl = "/login"; //授權確認頁面 預設/consent //options.UserInteraction.ConsentUrl = ""; }) .AddDeveloperSigningCredential() .AddInMemoryApiResources(IdpConfig.GetApis()) .AddInMemoryClients(IdpConfig.GetClients()) .AddTestUsers(TestUsers.Users) .AddInMemoryIdentityResources(IdpConfig.GetApiResources());
Configure()
// 要寫在 UseMvc()前面 app.UseIdentityServer(); app.UseMvcWithDefaultRoute();
二、客戶端
1、新增認證相關資訊(StartUp.cs)
ConfigureServices()
//關閉了 JWT 身份資訊型別對映 //這樣就允許 well-known 身份資訊(比如,“sub” 和 “idp”)無干擾地流過。 //這個身份資訊型別對映的“清理”必須在呼叫 AddAuthentication()之前完成 //區別可參考下面截圖 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //新增認證資訊 services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; //認證伺服器 options.Authority = "http://localhost:5002"; //去掉 https options.RequireHttpsMetadata = false; options.ClientId = "mvc client"; options.ClientSecret = "mvc secret"; //把 token 儲存到 cookie options.SaveTokens = true; options.ResponseType = OpenIdConnectResponseType.Code; //新增申請 許可權 ,這裡的 scope 必須在授權服務端定義的客戶端 AllowedScopes 裡 options.Scope.Clear(); options.Scope.Add("api1"); options.Scope.Add(OidcConstants.StandardScopes.OpenId); options.Scope.Add(OidcConstants.StandardScopes.Email); options.Scope.Add(OidcConstants.StandardScopes.Address); options.Scope.Add(OidcConstants.StandardScopes.Phone); options.Scope.Add(OidcConstants.StandardScopes.Profile); options.Scope.Add(OidcConstants.StandardScopes.OfflineAccess); options.Events = new OpenIdConnectEvents { OnAuthenticationFailed = context => { context.HandleResponse(); context.Response.WriteAsync("驗證失敗"); return Task.CompletedTask; } }; });
Configure()
//寫在 UseMvc() 前面 app.UseAuthentication(); app.UseMvcWithDefaultRoute();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); 區別
2、新增測試Controller
[Authorize] public class TestController : Controller { /// <summary> /// 測試從服務端認證 /// </summary> /// <returns></returns> public async Task<IActionResult> Private() { var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); var idToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken); var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); var code = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.Code); var model = new HomeViewModel { Infos = new Dictionary<string, string>() { {"AccessToken", accessToken }, {"IdToken", idToken }, {"RefreshToken", refreshToken }, {"Code", code } //code 是空 因為code 是一次性的 } }; return View(model); } /// <summary> /// 測試請求API資源(api1) /// </summary> /// <returns></returns> public async Task<IActionResult> SuiBian() { var accesstoken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); if (string.IsNullOrEmpty(accesstoken)) { return Json(new { msg = "accesstoken 獲取失敗" }); } var client = new HttpClient(); client.SetBearerToken(accesstoken); var httpResponse = await client.GetAsync("http://localhost:5001/api/suibian"); var result = await httpResponse.Content.ReadAsStringAsync(); if (!httpResponse.IsSuccessStatusCode) { return Json(new { msg = "請求 api1 失敗。", error = result }); } return Json(new { msg = "成功", data = JsonConvert.DeserializeObject(result) }); } }
三、API資源
參考上一篇《IdentityServer4 (1) 客戶端授權模式》
修改SuiBianController.Get()
[HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); }
四、測試
1、埠說明
【客戶端:5003】【 API :5001 】【 授權認證伺服器:5002】,並確認3個埠可以正常訪問
2、客戶端訪問
2.1 、輸入地址:http://localhost:5003/test/private 檢視是否可正常跳轉到授權伺服器,
2.2、正常跳轉,輸入賬號(alice)密碼(alice)並登陸。登陸成功,並顯示可授權的資訊
2.3、點選授權同意,返回相關授權資訊,並展示在頁面
2.4 、輸入地址 http://localhost:5003/test/suibian 訪問 API 資源,正確返回