OAuth是一種開發受權的網絡標準,全拼爲open authorization,即開放式受權,最新的協議版本是2.0。javascript
舉個栗子:html
有一個"雲沖印"的網站,能夠將用戶儲存在Google的照片,沖印出來。用戶爲了使用該服務,必須讓"雲沖印"讀取本身儲存在Google上的照片。java
傳統方法是,用戶將本身的Google用戶名和密碼,告訴"雲沖印",後者就能夠讀取用戶的照片了。這樣的作法有如下幾個嚴重的缺點。node
- "雲沖印"爲了後續的服務,會保存用戶的密碼,這樣很不安全。
- Google不得不部署密碼登陸,而咱們知道,單純的密碼登陸並不安全。
- "雲沖印"擁有了獲取用戶儲存在Google全部資料的權力,用戶無法限制"雲沖印"得到受權的範圍和有效期。
- 用戶只有修改密碼,才能收回賦予"雲沖印"的權力。可是這樣作,會使得其餘全部得到用戶受權的第三方應用程序所有失效。
- 只要有一個第三方應用程序被破解,就會致使用戶密碼泄漏,以及全部被密碼保護的數據泄漏。
因此OAuth就誕生了!git
- Third-party application:第三方應用程序,本文中又稱"客戶端"(client),即上一節例子中的"雲沖印"。
- HTTP service:HTTP服務提供商,本文中簡稱"服務提供商",即上一節例子中的Google。
- Resource Owner:資源全部者,本文中又稱"用戶"(user)。
- User Agent:用戶代理,本文中就是指瀏覽器。
- Authorization server:認證服務器,即服務提供商專門用來處理認證的服務器。
- Resource server:資源服務器,即服務提供商存放用戶生成的資源的服務器。它與認證服務器,能夠是同一臺服務器,也能夠是不一樣的服務器。
登陸層提供令牌(token)的生成,其中token包括:有效期、權限範圍。客戶端拿到token去訪問受限資源。github
- access_token:請求資源時須要攜帶的token,即訪問token。
- refresh_token:刷新token,若是access_token過時,可使用該token獲取一份新的access_token和新的refresh_token。通常refresh_token時效性較長,好比一年,而access_token時效性較短,好比幾分鐘。
- 權限範圍:即指定客戶端能夠獲取的資源權限範圍。
OAuth有四種受權模式,分別爲:json
- 受權碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
受權碼模式是最爲嚴密的受權模式,總體流程爲:瀏覽器攜帶必要信息至受權頁面,正常登陸成功後,返回一個code(受權碼),客戶端拿到code後在後臺獲取拿code換取token。瀏覽器
密碼模式,簡單地理解即爲使用用戶名密碼等參數獲取access_token,它的步驟以下:安全
- 用戶向客戶端提供用戶名和密碼。
- 客戶端將用戶名和密碼發給認證服務器,向後者請求令牌。
- 認證服務器確認無誤後,向客戶端提供訪問令牌。
refresh_token被用來獲取新的access_token和refresh_token,使用方式簡單以下:服務器
refresh_token無效:
技術棧:
- nodejs + eggjs
- eggjs-oAuth-server插件
具體能夠參考:
https://github.com/Azard/egg-oauth2-server
https://cnodejs.org/topic/592b2aedba8670562a40f60b
這裏咱們構建兩個站點,一個是7001端口(受權服務),一個是7002端口(客戶端),受權模式爲code grant。
首先是客戶端登陸頁:
單擊按鈕後直接登陸:
能夠發現,瀏覽器重定向到受權服務地址,並攜帶了response_type、client_id、redirect_uri三個參數,登陸成功後,瀏覽器會重定向到redirect_uri指定的地址,即這裏的http://127.0.0.1:7002/auth/redirect:
以下爲受權服務的登陸頁寫法
<form action="/oauth2/authorize?{{query}}" id="form1" name="f" method="post"> <div class="input_outer"> <span class="u_user"></span> <input name="username" class="text" style="color: #FFFFFF !important" type="text" placeholder="請輸入帳戶"> </div> <div class="input_outer"> <span class="us_uer"></span> <input name="password" class="text" style="color: #FFFFFF !important; position:absolute; z-index:100;"value="" type="password" placeholder="請輸入密碼"> </div> <div class="mb2"><a class="act-but submit" href="javascript:;" onclick="document.getElementById('form1').submit()" style="color: #FFFFFF">登陸</a></div> </form>
這裏的${query}即爲客戶端登陸重定向攜帶的完整query,而後是/oauth2/authorize路由的寫法:
app.all('/oauth2/authorize', app.oAuth2Server.authorize()); // 獲取受權碼
這裏調用app.oAuth2Server.authorize()時,插件會自動執行重定向操做,首先是重定向到客戶端指定地址,客戶端拿到code和state後,再去受權層獲取token:
async redirect(){ // 服務端重定向過來的 console.log(this.ctx.query) const result = await this.ctx.curl('http://127.0.0.1:7001/users/token', { dataType: 'json', // contentType: 'application/x-www-form-urlencoded', // 默認格式 method: 'POST', timeout: 3000, data: { grant_type: 'authorization_code', code: this.ctx.query.code, state: this.ctx.query.state, client_id: client_id, client_secret: client_secret, redirect_uri: redirect_uri, } }); this.ctx.body = result.data; }
獲取到token後正常返回:
首先使用username、password獲取access_token:
用戶名或密碼錯誤時返回:
使用token獲取受權資源正常返回:
以上內容完整源碼參考:https://github.com/caiya/eggjs-oAuth2-server
- OAuth實際使用時要上https,包括客戶端和受權服務端
- 受權服務可使用私鑰簽名,客戶端使用公鑰驗證,從而保證數據安全性