一. 概述
本篇探討使用"基于瀏覽器的JavaScript客戶端應(yīng)用程序"看幼。與上篇實(shí)現(xiàn)功能一樣,只不過這篇使用JavaScript作為客戶端程序,而非core mvc的后臺(tái)代碼HttpClient實(shí)現(xiàn)。 功能一樣:用戶首先要登錄IdentityServer站點(diǎn),再使用IdentityServer發(fā)出的訪問令牌調(diào)用We??b API半抱,可以注銷IdentityServer站點(diǎn)下登錄的用戶,清除cookie中的令牌信息膜宋。所有這些都將來自瀏覽器中運(yùn)行的JavaScript窿侈。
此示例還是三個(gè)項(xiàng)目:
IdentityServer令牌服務(wù)項(xiàng)目 http://localhost:5000
API資源項(xiàng)目 http://localhost:5001
JavaScript客戶端項(xiàng)目 http://localhost:5003
二. IdentityServer項(xiàng)目
1.1 定義客戶端配置
Config.cs中,定義客戶端秋茫,使用code 授權(quán)碼模式史简,即先登錄獲取code,再獲取token。項(xiàng)目其它處代碼不變肛著。
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// JavaScript Client
new Client
{
ClientId = "js",
ClientName = "JavaScript Client",
//授權(quán)碼模式
AllowedGrantTypes = GrantTypes.Code,
//基于授權(quán)代碼的令牌是否需要驗(yàn)證密鑰,默認(rèn)為false
RequirePkce = true,
//令牌端點(diǎn)請(qǐng)求令牌時(shí)不需要客戶端密鑰
RequireClientSecret = false,
RedirectUris = { "http://localhost:5003/callback.html" },
PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
//指定跨域請(qǐng)求,讓IdentityServer接受這個(gè)指定網(wǎng)站的認(rèn)證請(qǐng)求圆兵。
AllowedCorsOrigins = { "http://localhost:5003" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
}
三. API項(xiàng)目
在Web API項(xiàng)目中配置 跨域資源共享CORS。這將允許從http:// localhost:5003 (javascript站點(diǎn)) 到http:// localhost:5001 (API站點(diǎn)) 進(jìn)行Ajax調(diào)用(跨域)枢贿。項(xiàng)目其它處代碼不變殉农。
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服務(wù)
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客戶端項(xiàng)目
在項(xiàng)目中,所有代碼都在wwwroot下局荚,沒有涉及到服務(wù)端代碼超凳,可以完全不用core程序來調(diào)用。目錄如下所示:
其中添加了兩個(gè)html 頁(yè)(index.html, callback.html),一個(gè)app.js文件耀态,這些屬于自定義文件轮傍。oidc-client.js是核心庫(kù)。
4.1 index頁(yè)面
用于調(diào)用登錄首装、注銷创夜、和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
是應(yīng)用程序的主要代碼仙逻,包括:登錄挥下、Api請(qǐng)求揍魂,注銷。配置與服務(wù)端代碼差不多棚瘟,如下所示:
/// <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應(yīng)用程序
mgr.getUser().then(function (user) {
if (user) {
log("User logged in", user.profile);
}
else {
log("User not logged in");
}
});
//登錄
function login() {
mgr.signinRedirect();
}
//跨域請(qǐng)求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協(xié)議登錄握手。對(duì)應(yīng)app.js中config對(duì)象下的redirect_uri: "http://localhost:5003/callback.html"喜最。登錄完成后偎蘸,我們可以將用戶重定向回主index.html頁(yè)面。添加此代碼以完成登錄過程
<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>
五 測(cè)試
(1) 啟動(dòng)IdentityServer程序http://localhost:5000
(2) 啟動(dòng)API程序http://localhost:5001瞬内。這二個(gè)程序?qū)儆诜?wù)端
(3) 啟動(dòng)javascriptClient程序 http://localhost:5003
(4) 用戶點(diǎn)擊login迷雪,開始握手授權(quán),重定向到IdentityServer站點(diǎn)的登錄頁(yè)
(5) 輸入用戶的用戶名和密碼虫蝶,登錄成功章咧。跳轉(zhuǎn)到IdentityServer站點(diǎn)consent同意頁(yè)面
(6) 點(diǎn)擊 yes allow后,跳回到客戶端站點(diǎn)http://localhost:5003/index.html能真,完成了交互式身份認(rèn)證赁严。
(7) 調(diào)用點(diǎn)擊Call API按鈕,獲取訪問令牌粉铐,請(qǐng)求受保護(hù)的api資源疼约。調(diào)用CallAPI 時(shí),是訪問的api站點(diǎn)http://localhost:5001/identity蝙泼。
參考文獻(xiàn)