OAuth2.0授權詳解

  • 2019 年 10 月 3 日
  • 筆記

學習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.手動自己上去清吧

時間不早了,先寫到這裡吧,如果哪裡理解錯的歡迎指正,我會很感激不盡的。

你的關注是對我最大的支援~蟹蟹~