前言
上一篇Resource Owner Password Credentials模式雖然有用戶參與,但對於非信任的第三方的來說,使用這種模式是有風險的,所以相對用的不多;這里接着說說implicit隱式模式,這種模式比較適合於純前端客戶端,比如Vue、Angular、React項目等,相對來說整個流程比較安全,只需在認證服務器進行認證即可,無需在客戶端進行相關隱私信息錄入,但前提是要客戶端保證安全,不然容易被別人釣魚(安全很重要),那IdentityServer4不背這個鍋。
正文
既然有用戶參與,交互肯定少不了,認證那更不能缺少,所以這里使用OIDC(OpenID Connect)的協議進行用戶身份驗證;別跑,不管是客戶端還是認證服務器端,已經都封裝好了,拿過來直接用即可;
從流程先了解一下,直接上圖:
流程簡要說明:
- 用戶通過瀏覽器(User-Agent)訪問第三方客戶端(Client);
- 如果客戶端需要進行認證授權,則將其重定向到認證服務器(Authorization server);
- 用戶(ResorceOwner)在認證服務器上進行認證;
- 認證服務器(Authorization server)驗證成功之后,返回AccessToken和Id Token客戶端;
- 后續用戶再操作客戶端的時候,若需訪問保護資源,直接在請求中帶上AccessToken即可;
- 最終資源服務器(Resource server)返回對應信息;
術語解釋:
- User-Agent:這里指就是瀏覽器;如果客戶端是Web,就需要用戶通過瀏覽器訪問客戶端,所以這里的瀏覽器就是User-Agent;
- Id Token:標識用戶身份的Token,這是OpenID Connect帶上的。
知道大概的流程,接下來就通過代碼實踐的方式演示一把。
1.拷貝上一節Reource Owner Password的授權服務器和資源服務器代碼並完善;
對於資源服務器來說,這里先暫時不用修改其他;
認證服務器增加界面支持
而認證服務器需要有自己的頁面,因為在訪問客戶端時會重定向到認證服務器進行認證信息錄入和授權相關操作,那頁面需要自己寫嗎,不需要的,IdentityServer4模板中已經准備好了,如下操作:
-
方式一:直接從github上下載,下載地址:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/tree/main;
-
方式二:命令行方式,從IdentityServer4模板中獲取;
dotnet new -i IdentityServer4.Templates ##先安裝模板,里面有很多關於IDS4的模板 dotnet new is4ui ## 指定UI模板獲取,
通過以上兩種方式都可以獲取到以下文件,將其拷貝到認證服務器項目中,如下:
由於之前的認證服務器只是單純API項目,不支持MVC,因為增加的界面是MVC頁面,所以需要在Startup.cs中增加MVC的支持,頁面中需要樣式文件的加載,則同時需要增加靜態文件處理,如下:
運行起來看效果,如下:
備案客戶端
上面界面沒問題了,這里打算做一個純前端(Vue)的客戶端進行測試,所以需要在認證服務器中提前將客戶端進行備案,如下:
2. 增加純前端客戶端(Vue)
這里不打算使用腳手架工具去生成項目,為了不喧賓奪主,這里就很單純的寫html和Js,只是在其中引用Vue,用Vue的思路進行編碼演示;
既然是個純前端項目,得要有個服務器作為宿主,然后通過URL地址訪問頁面,這里不想用其他比較重的服務器,live-server簡單好用,就用它了,簡單搭建一下環境:
環境准備
我是用npm安裝,所以需要安裝nodejs,這里就不詳細擴展了,具體安裝步驟詳見:https://www.runoob.com/nodejs/nodejs-install-setup.html;
有了nodejs環境,直接npm安裝live-server,如下命令:
npm install -g live-server ###全局安裝live-server,后續都可以用
安裝完就可以直接使用了,直接在指定目錄下執行live-server即可,默認端口是8080;
創建純前端客戶端
項目目錄結構如下:
簡要說明:
-
js目錄:存放依賴的js;
live-server-https目錄是提供live-server啟動時提供live-server-https支持,其實項目本身並不需要;后續如果需要將前端頁面指定https訪問,可以指定此庫運行;
oidc-client.js/oidc-client.min.js是用於前端客戶端實現OpenID Connect,引入直接使用即可,也可以自行在網上下載(https://github.com/IdentityModel/oidc-client-js);
vue.js提供vue的支持,雖然沒用腳手架,但用vue還是沒問題;
-
callback.html登錄成功之后回調的頁面,即當在認證服務器登錄成功之后重定向客戶端的頁面;
-
index.html主頁面,這里也用於登出時重定向的頁面;
-
noauth.html無權限時跳轉的頁面;
代碼主要集中在index.html,下面直接上代碼吧,具體說明直接看注釋吧;
index.html代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div id="app">
<button id="login" @click="login">Login</button>
<button id="api" @click="callApi">Call API</button>
<button id="logout" @click="logout">Logout</button>
<!-- 顯示日志,包含登入、登出和請求API結果 -->
<pre id="results">{{results}}</pre>
</div>
<script src="./js/oidc-client.js"></script>
<script src="js/vue.js"></script>
<!-- <script src="./js/app.js"></script> -->
<script>
var vm = new Vue({
el: "#app",
created() {
// 根據配置信息創建用戶管理對象,后續直接使用
this.mgr = new Oidc.UserManager(this.config);
},
mounted() {
// 這里要注意this的使用
var that = this;
this.mgr.getUser().then(function(user) {
if (user) { // 判斷是否登錄成功
that.log("User logged in", user.profile);
} else { // 沒登錄
that.log("User not logged in");
}
});
},
data: {
// 客戶端配置
config: {
authority: "http://localhost:6100",
client_id: "JsClient",
redirect_uri: "http://localhost:8080/callback.html",
response_type: "id_token token",
scope: "openid profile orderApi", //客戶端需要的權限范圍
post_logout_redirect_uri: "http://localhost:8080/index.html",
},
mgr: null, //登錄對象
results: "" //登錄通過數據
},
methods: {
// 登錄
login: function() {
console.log("login");
this.mgr.signinRedirect();
},
callApi: function() {
console.log("CallAPI");
var that = this;
// 先判斷是否登錄,登錄之后在去調用API
this.mgr.getUser().then(function(user) {
var url = "http://localhost:5000/api/Order";
// 這里使用原生異步請求,就是引入第三方ajax庫啦
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function() {
// 顯示請求API獲取到的數據
that.log(xhr.status, JSON.parse(xhr.responseText));
};
xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
// 發送請求
xhr.send();
});
},
logout: function() {
console.log("logout");
// 登出
this.mgr.signoutRedirect();
},
// 將信息展示
log: function() {
var that = this;
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);
}
that.results = msg + '\r\n';
});
}
}
});
</script>
</body>
</html>
callback.html代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script src="./js/oidc-client.js"></script>
<script>
new Oidc.UserManager().signinRedirectCallback().then(function() {
console.log("test");
// 如果登錄成功就回到主頁面
window.location = "index.html";
}).catch(function(e) {
console.log(e);
//沒有權限就跳轉到無權限頁面
window.location="noauth.html";
});
</script>
</body>
</html>
noauth.html代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div>
沒權限
</div>
</body>
</html>
到這客戶端的代碼已經擼完了,直接進入JsClient目錄,執行live-server即可:
如果使用http請求,執行live-server
即可;
3.聯合調試看效果
上一步已經將客戶端啟起來了,接下來把認證服務器和API資源服務器啟動,看看效果:
-
點擊Login進行登錄,就會跳轉到認證服務器進行驗證,如下:
到這一步,估計用其他瀏覽器沒什么問題,會繼續進行跳轉;但如果用到谷歌最新的瀏覽器,這里就不會跳轉,因為谷歌對Cookie進行整改,盡管輸入正確的用戶名和密碼,也只是刷新一下,還是在登錄頁面,需要做一下處理,如下:
新增一個處理類:SameSiteCookiesServiceCollectionExtensions.cs,代碼內容就不貼啦,這是公開代碼,大佬寫好的解決方案;
寫好處理類之后,直接使用即可,如下:
完成以上步驟,谷歌不能跳轉的問題就解決啦; 繼續往下走;
-
輸入用戶名和密碼 Zoe/123456,點擊登錄,直接進入授權選擇頁面,如下:
這個界面是不是比較熟,通常我們用微信、QQ或者是其他登錄時,都會彈出一個授權頁面選擇授權內容。這個授權頁面是可以控制不顯示的,只需要在認證服務器備案客戶端時設置屬性即可,如下:
-
點擊授權完成之后,就會重定向配置的callback.html頁面,在callback.html代碼中可以看到,如果判斷用戶已經登錄,就直接轉到index.html主頁面,否則就轉到noauth.html無權限頁面,這里已經登錄授權成功,肯定最終就轉到index.html頁面,如下:
-
點擊CallAPI去調用受保護資源,內部是根據上一步得到AccessToken進行保護資源的訪問;
注意,這里資源服務器一定要進跨域配置,允許客戶訪問,在API資源服務器中進行如下配置:
然后運行效果如下:
-
點擊Logout登出,這里不截圖了,小伙伴試試;
好吧,今天狀態不佳,先到這,后續項目實戰再好好說說。
源碼地址:https://github.com/zyq025/IDS4Demo/tree/main/ImlicitDemo;
總結
Implicit模式使用思路差不多就是這樣,但這種模式特別要注意項目本身安全,如數據傳輸過程被攔截,或網站本身容易被攻擊,黑客通過釣魚頁面就容易獲取隱私信息等,所以項目通常都強烈推薦https,降低數據傳輸過程被抓包的風險;而對於釣魚等這種攻擊,可以通過判斷校驗源IP等方式;安全的點很多,這里只是簡單舉例;下次說說Authorization Code(授權碼)模式;
一個被程序搞丑的帥小伙,關注"Code綜藝圈",跟我一起學~