前言
最近再學習微服務,所以把自己的個人站點https://www.ttblog.site/拆分成微服務。目前正在思考微服務里面的認證與授權,網上百度到都是根據用戶名和密碼來實現的,考慮到實際的原因,我的個人站點是最先訪問不需要登錄,當執行寫入或更改操作時才需要用戶名和密碼,所以我自己思考了一個方案,這里分享一下,設計難免有很多不合理之處,大家可以予以批評。
文檔
我開始做的時候,對認證授權不是很理解,所以我在網上百度並且在博客園和開源中國提了一下問。https://www.oschina.net/question/2859520_2319077和https://q.cnblogs.com/q/129422/。並且看了很多文章,大多數使用的是IdentityServer4,但是我發現這個比較復雜,貌似還要安裝一些認證,所以選擇了使用JWT。並且了解了一下OAuth2,我覺得我用的應該屬於里面的客戶端模式https://www.jianshu.com/p/84a4b4a1e833。大概都了解之后,我就開始在項目里集成了jwt和ocelot。
實戰
首先創建了一個認證服務器
BlogAuthApi
然后一個網關
BlogGateway
最后一個
BlogWebApi
我的思路就是js判斷是否存有token,如果沒有在請求認證服務器Auth,,返回一個token,存入瀏覽器,然后之后通過token去訪問webapi。
1,請求token
我這里使用的微服務網關屬於Ocelot,請求時通過網關轉發到認證服務器獲取token,如下代碼生成token:
並且添加配置文件
然后前端獲取到token之后會吧token放入到header里面請求。
2,配置網關服務Ocelot
使用ocelot認證時,需要配置Ocelot.json,對相應的路由添加節點
讓后需要在Startup里面添加Jwt如下:
之后啟動3個服務來測試下,
當我們不傳token時,請求時直接返回401的:
然后我們請求認證服務器獲取token
然后我們把token放入header里面請求:
可以看到請求成功了,並且我們可以看到token的過期時間為120秒,然后過了兩分鍾我們在請求就不行了
到此,我的api認證功能已經大致完成了,因為自己並沒有這方面的經驗,例如怎么token過期了前端怎么取刷新的問題,怎么擴展ocelot過期返回的response等等,自己都是要一點一點去學習了解的,這里只是貼出我的過程,和大家分享討論下,希望可以給出好的意見。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2020/09/30 16:43
這里作為后續的更新,
之前我已經講過如何認證token,然后我下午又想了一個刷新token的方法,講一下我的思路。
首先是認證服務器返回token,過期時間,token創建時間3個字段,然后前端存儲這3個字段。
例如當一次請求的時候token為空,則請求token並存儲,第二次請求時如果token有值,則根據過期時間,token創建時間來判斷是否過期沒如果過期了就在此請求token,並且更新當前的localStorge,代碼如下
/**
* 字符串轉日期
*/
function stringToDate(str){
var tempStrs = str.split(" ");
var dateStrs = tempStrs[0].split("-");
var year = parseInt(dateStrs[0], 10);
var month = parseInt(dateStrs[1], 10) - 1;
var day = parseInt(dateStrs[2], 10);
var timeStrs = tempStrs[1].split(":");
var hour = parseInt(timeStrs [0], 10);
var minute = parseInt(timeStrs[1], 10);
var second = parseInt(timeStrs[2], 10);
var date = new Date(year, month, day, hour, minute, second);
return date;
}
/**
* 全局ajax處理
*/
layui.use('layer', function () {
var layer = layui.layer;
$.ajaxSetup({
cache: false,
beforeSend: function (xhr) {
var token = localStorage.getItem('token');//token
var tokenExpireTime = localStorage.getItem('tokenExpireTime');//過期時間
var tokenSaveTime = localStorage.getItem('tokenSaveTime');//token保存時間
var requestToken = false;//是否需要獲取token
if (token == undefined || tokenExpireTime == undefined || tokenSaveTime == null) {
requestToken = true;
}
if (!requestToken) {//不需要時判斷token是否過期
if (tokenExpireTime == undefined) {
requestToken = true;
}
else {
var now = new Date();
tokenSaveTime = stringToDate(tokenSaveTime);
var s=now.getTime()-tokenSaveTime.getTime();
//計算出相差天數
var days=Math.floor(s/(24*3600*1000))
//計算出小時數
var leave1=s%(24*3600*1000) //計算天數后剩余的毫秒數
var hours=Math.floor(leave1/(3600*1000))
//計算相差分鍾數
var leave2=leave1%(3600*1000) //計算小時數后剩余的毫秒數
var minutes=Math.floor(leave2/(60*1000))
//計算相差秒數
//var leave3=leave2%(60*1000) //計算分鍾數后剩余的毫秒數
//var seconds=Math.round(leave3/1000)
if(days>0)
{
requestToken = true
}
else if(hours>0){
requestToken = true
}
else if(minutes>tokenExpireTime){
requestToken = true
}
}
}
if (requestToken) {
$.ajax({
url: api + '/auth/token',
type: 'get',
datatype: 'json',
async:false,
beforeSend: function () {
var i=1;防止調用token時會通過ajaxStup再次執行beforeSend
},
success: function (res) {
if (res.code == 200) {
token=res.data.token;
localStorage.setItem('token', res.data.token);
localStorage.setItem('tokenExpireTime', res.data.expireMinutes);
localStorage.setItem('tokenSaveTime', res.data.createTime);
}
},
complete: function () {
var i=1;
}
})
}
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
},
error: function (request) {
layer.msg('響應服務器失敗', {
icon: 7
});
},
});
})
