學習oauth認證之前先回顧一下通過sessionid的會話過程
關於session與cookie的請戳:https://www.cnblogs.com/moran1992/p/10793748.html
那么這種利用session的會話方式會引發哪些問題呢
1.安全問題
常見保存會話方式:cookie,session以及token等等,這里我們將這三種方式的安全性能都簡單的分析一下嘿嘿~
cookie:我們了解cookie的安全問題首先從客戶端頒發cookie開始,然后通過set-cookie響應給客戶端,客戶端保存之后下次通信會以默認的方式攜帶cookie,這種東西是程序員不可控的,這時候會發生一個什么問題,當網頁中的一段link代碼發生XSS攻擊時,會獲取到本
地cookie。那么問題來了。為啥?因為cookie默認寫到http的headers里,沒法控制。那么好,我們退出登錄,這時候服務器對cookie怎么操作的,通過set-cookie的方式清空cookie並返回到客戶端。這樣導致一個問題,已經被劫持了的cookie還能用,這就是為啥cookie不
安全的原因。
session:首先我們要知道,session到底是個啥,我的理解是session就是cookie的另一種形式,同樣session是服務器頒發給客戶端的,同樣通過響應中的set-cookie,但是與此同時服務器還會保存這個session id,本質上都是通過cookie傳遞,本質上都可以被上訴攻擊方式攻擊,但是好處在哪,當退出登錄的時候會服務器對session id會有個刪除操作。這樣被劫持的session id其實就沒用了,但是不管怎樣,承擔的風險方式與cookie是一樣的。
token:session與cookie都存在風險,那么更好的解決方案是什么呢,就是token,放在后面說。
2.服務器壓力過大
session的特點之一就是存在服務器端,請求過多的時候session也會增多,這就會導致服務器的壓力過大。
3.分布式session管理困難
現在幾乎所有的web應用程序都會采用分布式的處理方案,那么每次一起請求不一定發送到哪個服務器,比如這次請求發送的Server A上並且保存了session,下一個請求我發Server B上了,Server B沒有啊,怎么校驗,這就會引發另一個問題,分布式下如何共享session。當然現在有好多的方式比如:Session Replication方式(session復制),緩存集中方式管理(將Session集中放在一個服務器上,我們也可以稱其session服務器),基於Redis進行session共享等等,很明顯服務器壓力很大,而且並不好管理。
4.跨域問題
首先啥叫跨域?域名不同,協議不同,或者端口號不同通通稱為跨域,跨域產生於瀏覽器的同源策略,請求發出去了,但是響應被攔截了。攜帶headers里面的cookie是不可以跨域的,但是authorization可以啊,token存在哪?token就存在authorization里啊,emmmm~好處顯而易見。
說了這么多session的缺點,時代總是向前發展的,技術也一樣呀,於是乎token誕生了~
什么是token
token可以理解成一個帶有User基本信息的令牌,例如每一次會話調用服務器的API時,服務器可以通過token判斷是否有權限。
token流程
token種類
id token(不知道干啥用的,沒用過,見過存session id的)
access token(用來做權限認證,生命周期相對比較短)
refresh token(用來生成新的access token,生命周期相對比較長)
token特點有哪些
1.安全性能更好
之前列舉了cookie和session的安全性能問題,那么token為啥就安全了?首先token是程序員自己寫入http headers里的,並不是像cookie一樣自動寫入http的headers里的,如果沒必要的我們不寫入,XSS攻擊方式是獲取不到token的。
2.無狀態(多個服務共享,減輕服務器的鴨梨)
token是無狀態的,也就是說token並不存入服務器中(如果想存,請便)當服務器認證過后,會生成一個token返回給用戶,並且存入瀏覽器緩存里(localstorage等),接下來的請求會我們會獲取這個token並放入authorization內,服務器接收到請求,並解析token來判斷當前請求是否有權限調用api,這里引出另一個概念鑒權。
3.跨程序調用(避開同源策略)
當token變成無狀態,只要一個token,就可以在任何一個服務器上認證(解析方式必須一致,其實都是代碼層面的東西)於是我們可以采用一種設計方式叫,分離認證服務與業務任務。當我們可以通過認證服務器來獲取token,然后發送給業務服務器的時候校驗token,這時會出現兩種方式,第一種是在業務服務器校驗token,另一種是毎一次請求通過中間層再發送給認證服務器進行校驗,兩種設計方式各有優缺點,不管怎樣都是實現了跨程序共享token,咦沒有跨域問題哦(當然需要服務器配合嚶嚶嚶~)到這里就引出另一個概念,OAuth認證。
什么是OAuth認證
OAuth是一個關於授權(authorization)的開放網絡標准,目前的版本是2.0版,即OAuth2.0
OAuth的應用場景
一個公司往往會有很多系統,比如HR系統,你的請假吧,比如公司的內部員工網站,得有業余活動吧,再比如公司好多產品,但是會有個產品是可以登錄所有網站的吧,總不能好多產品分成好多認證系統吧,能用就一個會節約多少人力物力呢~這里會引出一個概念SSO(單點登錄)SSO其實是一種解決方案,俺們公司的叫AOS系統。這個通過一個系統登錄定向到各個產品的授權過程就是OAuth認證。
OAuth認證設計方式(這里列兩種,高大上的我也不知道):
業務服務器校驗token
優點:相對於第二種不用每一次請求都要通過鑒權服務器,可以保證token的新鮮度。
缺點:效率低。
認證服務器校驗token
優點:效率高
缺點:如果是個第三方的鑒權服務token信息更新不及時
整個鑒權過程都是通過access token,由於有效時間比較短,如果我想要半個月登錄一次呢,也就說半個月之內不需要重新登錄,怎么辦refresh token的作用就產生了。
refresh token做了些什么事兒呢
通過獲取access token的有效時間,判斷當access token馬上過期的時候,這時候我可以通過refresh token從權限服務器獲取新的access token,這都可以偷摸的做了,反正使用者不會知道。
1.客戶端發送refresh token請求
2.服務器發送refresh token
token的實現方式
現有的token解析方式其實並不唯一,但是有個很出名的那就是jwt(json web token)
什么是jwt
可以理解jwt是token的一種規則
jwt的組成
1.Header 2.Payload 3.Signature
jwt的特點(來自阮一峰)
JWT 默認是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。
JWT 不加密的情況下,不能將秘密數據寫入 JWT。
JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。
JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。
JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
為了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。
栗子(github登錄本地站點)
技術棧:nodejs
獲取github的token過程
1.注冊github應用
找到settings
找到OAuths Apps
注冊成功
2.獲取github授權碼
這里我們需要將注冊過得client id跟回調函數當做參數傳入
handleGitHubLogin() { let path = `https://github.com/login/oauth/authorize?response_type=code&redirect_uri=${CommonUtil.Config.github.redirect_uri}&scope=user%2Crepo&client_id=${CommonUtil.Config.github.client_id}`; location.href = path; }
這時候會跳到github的授權頁面
授權成功后會返回一個code(授權碼)
通過code clientId clientSecret獲取accessToken
router.post("/oAuthValidate", (req, res) => { let { clientId, clientSecret, code } = req.body; axios({ method: "post", url: "https://github.com/login/oauth/access_token?" + `client_id=${clientId}&` + `client_secret=${clientSecret}&` + `code=${code}`, headers: { accept: "application/json" } }) .then(tokenResponse => { let accessToken = tokenResponse.data.access_token; getGitHubToken(accessToken, res); }) .catch(e => { console.log(e); }); });
由於整個應用程序采用SAP,所以整個流程通過前台獲取最合理,但前台獲取token必然會出現一個問題就是跨域,那么如何解決前端跨域資源共享問題呢,這里提供一個解決方案就是Gatekeeper,github自己去找吧,使用方式之后補上。
通過accessToken獲取User信息
function getGitHubToken(accessToken, res) { axios({ method: "get", url: `https://api.github.com/user`, headers: { accept: "application/json", Authorization: `token ${accessToken}` } }) .then(result => { let token = jwt.sign(result.data, "my_token", { expiresIn: "1h" }); util.responseClient(res, 200, 0, "獲取github token成功", { profileInfo: result.data, accessToken: token }); }) .catch(e => { util.responseClient(res, 500, 0, "get github token failed.", { message: e }); }); }
這里為了使用jwt,我並沒有使用github的accessToken,而是在自己的應用程序里使用了jwt,可以自行選擇。
返回給瀏覽器並且保存到緩存里
再次請求的時候進行token鑒權
app.use( expressjwt({ secret: "my_token", credentialsRequired: true, //如果false 則authoriaztion為空時也通過。 getToken: function fromHeaderOrQuerystring(req) { if ( req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer" ) { var token = req.headers.authorization.split(" ")[1]; return token; } else if (req.query && req.query.token) { return req.query.token; } return null; } }).unless({ path: util.whiteList }) ); function checkPromisition(req, res, next) { if (1) { return next(); } else { util.responseClient(res, 500, 0, "delete github token failed.", { log: "delete github token failed with http code 401." }); } } app.use(function (err, req, res, next) { if (err.name === "UnauthorizedError") { util.responseClient(res, 403, 0, "invalid token...", {}); } }); app.use("/leavemessage",checkPromisition,require("./leavemessage"));
jwt使用方式
引入jwt中間件
const jwt = require("jsonwebtoken");
jwt生成
let token = jwt.sign(result.data, "my_token", { expiresIn: "1h" });
鑒權部分
引入中間件
const expressjwt = require("express-jwt");
具體細節看上面代碼吧
這里有些問題首先github並沒有給我refresh token,這里是我沒找到嗎,請知道的大佬指點一二,其次github沒有暴露鑒權的接口嗎
撤銷github的accesstoken方式
1.清緩存
2.手動自己上去清吧
時間不早了,先寫到這里吧,如果哪里理解錯的歡迎指正,我會很感激不盡的。
你的關注是對我最大的支持~蟹蟹~