前言
最近再學習微服務,所以把自己的個人站點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 }); }, }); })