一. 概述 本篇探討使用"基於瀏覽器的JavaScript客戶端應用程式"。與上篇實現功能一樣,只不過這篇使用JavaScript作為客戶端程式,而非core mvc的後臺代碼HttpClient實現。 功能一樣:用戶首先要登錄IdentityServer站點,再使用IdentityServer發出 ...
一. 概述
本篇探討使用"基於瀏覽器的JavaScript客戶端應用程式"。與上篇實現功能一樣,只不過這篇使用JavaScript作為客戶端程式,而非core mvc的後臺代碼HttpClient實現。 功能一樣:用戶首先要登錄IdentityServer站點,再使用IdentityServer發出的訪問令牌調用Web API,可以註銷IdentityServer站點下登錄的用戶,清除cookie中的令牌信息。所有這些都將來自瀏覽器中運行的JavaScript。
此示例還是三個項目:
IdentityServer令牌服務項目 http://localhost:5000
API資源項目 http://localhost:5001
JavaScript客戶端項目 http://localhost:5003
二. IdentityServer項目
1.1 定義客戶端配置
Config.cs中,定義客戶端,使用code 授權碼模式,即先登錄獲取code,再獲取token。項目其它處代碼不變。
public static IEnumerable<Client> GetClients() { return new List<Client> { // JavaScript Client new Client { ClientId = "js", ClientName = "JavaScript Client", //授權碼模式 AllowedGrantTypes = GrantTypes.Code, //基於授權代碼的令牌是否需要驗證密鑰,預設為false RequirePkce = true, //令牌端點請求令牌時不需要客戶端密鑰 RequireClientSecret = false, RedirectUris = { "http://localhost:5003/callback.html" }, PostLogoutRedirectUris = { "http://localhost:5003/index.html" }, //指定跨域請求,讓IdentityServer接受這個指定網站的認證請求。 AllowedCorsOrigins = { "http://localhost:5003" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" } } }; }
三. API項目
在Web API項目中配置 跨域資源共用CORS。這將允許從http:// localhost:5003 (javascript站點) 到http:// localhost:5001 (API站點) 進行Ajax調用(跨域)。項目其它處代碼不變。
public void ConfigureServices(IServiceCollection services) { services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.Audience = "api1"; }); //添加Cors服務 services.AddCors(options => { // this defines a CORS policy called "default" options.AddPolicy("default", policy => { policy.WithOrigins("http://localhost:5003") .AllowAnyHeader() .AllowAnyMethod(); }); }); }
public void Configure(IApplicationBuilder app) { //添加管道 app.UseCors("default"); app.UseAuthentication(); app.UseMvc(); }
四. JavaScript客戶端項目
在項目中,所有代碼都在wwwroot下,沒有涉及到服務端代碼,可以完全不用core程式來調用。目錄如下所示:
其中添加了兩個html 頁(index.html, callback.html),一個app.js文件,這些屬於自定義文件。oidc-client.js是核心庫。
4.1 index頁面
用於調用登錄、註銷、和api。引用了oidc-client.js和app.js
<body> <button id="login">Login</button> <button id="api">Call API</button> <button id="logout">Logout</button> <pre id="results"></pre> <script src="oidc-client.js"></script> <script src="app.js"></script> </body>
4.2 app.js
是應用程式的主要代碼,包括:登錄、Api請求,註銷。配置與服務端代碼差不多,如下所示:
/// <reference path="oidc-client.js" /> //消息填充 function log() { document.getElementById('results').innerText = ''; Array.prototype.forEach.call(arguments, function (msg) { if (msg instanceof Error) { msg = "Error: " + msg.message; } else if (typeof msg !== 'string') { msg = JSON.stringify(msg, null, 2); } document.getElementById('results').innerHTML += msg + '\r\n'; }); } document.getElementById("login").addEventListener("click", login, false); document.getElementById("api").addEventListener("click", api, false); document.getElementById("logout").addEventListener("click", logout, false); var config = { authority: "http://localhost:5000", client_id: "js", redirect_uri: "http://localhost:5003/callback.html", response_type: "code", scope:"openid profile api1", post_logout_redirect_uri : "http://localhost:5003/index.html", }; //UserManager類 var mgr = new Oidc.UserManager(config); //用戶是否登錄到JavaScript應用程式 mgr.getUser().then(function (user) { if (user) { log("User logged in", user.profile); } else { log("User not logged in"); } }); //登錄 function login() { mgr.signinRedirect(); } //跨域請求api function api() { mgr.getUser().then(function (user) { var url = "http://localhost:5001/identity"; var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function () { log(xhr.status, JSON.parse(xhr.responseText)); } xhr.setRequestHeader("Authorization", "Bearer " + user.access_token); xhr.send(); }); } //註銷 function logout() { mgr.signoutRedirect(); }
4.3 callback.html
用於完成與IdentityServer的OpenID Connect協議登錄握手。對應app.js中config對象下的redirect_uri: "http://localhost:5003/callback.html"。登錄完成後,我們可以將用戶重定向回主index.html頁面。添加此代碼以完成登錄過程
<body> <script src="oidc-client.js"></script> <script> new Oidc.UserManager({ response_mode: "query" }).signinRedirectCallback().then(function () { window.location = "index.html"; }).catch(function (e) { console.error(e); }); </script> </body>
五 測試
(1) 啟動IdentityServer程式http://localhost:5000
(2) 啟動API程式http://localhost:5001。這二個程式屬於服務端
(3) 啟動javascriptClient程式 http://localhost:5003
(4) 用戶點擊login,開始握手授權,重定向到IdentityServer站點的登錄頁
(5) 輸入用戶的用戶名和密碼,登錄成功。跳轉到IdentityServer站點consent同意頁面
(6) 點擊 yes allow後,跳回到客戶端站點http://localhost:5003/index.html,完成了互動式身份認證。
(7) 調用點擊Call API按鈕,獲取訪問令牌,請求受保護的api資源。調用CallAPI 時,是訪問的api站點http://localhost:5001/identity。
參考文獻