摘要:看了好多,頭疼,自己寫一個吧。
一、基礎
1、nodejs
2、node-oauth2-server (node 模塊)
3、mysql(數據庫)
二、介紹
- 協議
OAuth 協議為用戶資源的授權提供了一個安全的、開放而又簡易的標准。
優點:OAuth的授權不會使第三方觸及到用戶的帳號信息(如用戶名與密碼),即第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權。
流程:用戶登錄了第三方的系統后,會先跳去服務方獲取一次性用戶授權憑據,再跳回來把它交給第三方。 第三方的服務器會把授權憑據以及服務方給它的的身份憑據一起交給服務方,這樣,服務方一可以確定第三方得到了用戶對此次服務的授權(根據用戶授權憑據)二可以確定第三方的身份是可以信任的(根據身份憑據)。所以,最終的結果就是,第三方順利地從服務方獲取到了此次所請求的服務。
三、術語
了解 OAuth2.0 與 oauth2-server 的專用術語,對於理解后面內容很有幫助。
OAuth2.0 定義了四個角色
- Client:客戶端,第三方應用程序。
- Resource Owner:資源所有者,授權 Client 訪問其帳戶的用戶。
- Authorization server:授權服務器,服務商專用於處理用戶授權認證的服務器。
- Resource server:資源服務器,服務商用於存放用戶受保護資源的服務器,它可以與授權服務器是同一台服務器,也可以是不同的服務器。
oauth2-server
- Access token:用於訪問受保護資源的令牌。
- Authorization code:發放給應用程序的中間令牌,客戶端應用使用此令牌交換 access token。
- Scope:授予應用程序的權限范圍。
- JWT:Json Web Token 是一種用於安全傳輸的數據傳輸格式。
四、授權模式
OAuth2.0 定義了四種授權模式,以應對不同情況時的授權。
- 授權碼模式(authorization code)
- 隱式授權模式,簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
五、客戶端類型
- 保密的:
- 客戶端可以安全的存儲自己與用戶的憑據(例如:有所屬的服務器端)
- 公開的:
- 客戶端無法安全的存儲自己與用戶的憑據(例如:運行在瀏覽器的單頁應用)
六、選用哪種授權模式?
- 如果客戶端是保密的,應使用授權碼模式。
- 如果客戶端是公開的,應使用隱式授權模式。
- 如果用戶對於此客戶端高度信任(例如:第一方應用程序或操作系統程序),應使用密碼模式。
- 如果客戶端是以自己的名義,不與用戶產生關系,應使用客戶端模式。
七、預先注冊
客戶端需要預先在授權服務器進行注冊,用以獲取 client_id
與 client_secret
,也可以在注冊是預先設定好 redirect_uri
,以便於之后可以使用默認的 redirect_uri
。
八、授權碼模式
授權碼模式是 OAuth2.0 種功能最完整,流程最嚴密的一種模式,如果你使用過 Google 或 QQ 登錄過第三方應用程序,應該會對這個流程的第一部分很熟悉。
- 流程
- 獲取授權碼。
- /oauth2/authorize為前端提交賬戶密碼的路由,將以下參數通過
GET query
傳入response_type
:授權類型,必選項,值固定為:code
client_id
:客戶端ID,必選項redirect_uri
:重定向URI,可選項,不填寫時默認預先注冊的重定向URIscope
:權限范圍,可選項,以空格分隔state
:CSRF令牌,可選項,但強烈建議使用,應將該值存儲與用戶會話中,以便在返回時驗證
- oAuth2Server是你封裝的獲取授權碼的模塊。
- authorizeHandler()是獲取授權碼code的函數。
code
:授權碼(Authorization code)state
:請求中發送的state
,原樣返回。客戶端將此值與用戶會話中的值進行對比,以確保授權碼響應的是此客戶端而非其他客戶端程序
- redirectUriWithCode()是服務端重定向至客戶端的函數
- /oauth2/authorize為前端提交賬戶密碼的路由,將以下參數通過
// 路由文件
app.all('/oauth2/authorize', oAuth2Server.authorizeHandler(), oAuth2Server.redirectUriWithCode()); // 獲取授權碼
// 封裝的oauth2文件
const oauthServer = require('oauth2-server');
import OAuthError from 'oauth2-server/lib/errors/oauth-error';
const Request = oauthServer.Request;
const Response = oauthServer.Respoconst oauth = new OauthServer({
model: oauthModel, allowEmptyState: true, authenticateHandler: { handle: function(req) { if (!req.user) { throw new OAuthError('需要登錄', { code: 400 }); } return req.user; }, }, }); /** * 授權獲取code * @param req * @param res * @param next * @returns {Promise<*>} */ export async function authorizeHandler(req, res, next) { log.info(`authorizeHandler: ${req.url}`); const request = new Request(req); const response = new Response(res); try { const code = await oauth.authorize(request, response);// log.info('finish authorization: ', code); res.locals.oauth = { code: code }; next(); } catch (err) { log.error(new OAuthError('authorizeHandler Error'), err); return res.status(500).json(err); } } /** * 帶着code重定向到指定URL * * req.query.redirect_uri - 重定向URL * res.locals.oauth - oauth信息 */ async function redirectUriWithCode(req, res, next) { try { const { state, client_id: clientId } = req.query; const { code } = res.locals.oauth; if (!code || !code.authorizationCode) { throw new OAuthError('獲取code失敗', { code: 400 }); } else if (!req.query.redirect_uri) { throw new OAuthError('redirect_uri不能為空', { code: 400 }); } else { res.redirect(req.query.redirect_uri, { code: code.authorizationCode, state })); //url好像需要urlencode轉碼,這里不寫了 } } catch (e) { next(e); } }
2.3、客戶端使用code向服務端獲取token
- /oauth2/tokenHandler為前端提交賬戶密碼的路由。通過
POST
請求向授權服務器獲取訪問令牌(access token)rant_type
:授權模式,值固定為:authorization_code
client_id
:客戶端IDclient_secret
:客戶端 secretredirect_uri
:使用與第一部分請求相同的 URIcode
:第一部分所獲的的授權碼,要注意URL解碼
- oAuth2Server是你封裝的獲取授權碼的模塊。
- tokenHandler()是獲取授權碼token的函數。
token_type
:令牌類型,值固定為:Bearer
-
expires_in
:訪問令牌的存活時間 -
access_token
:訪問令牌 -
refresh_token
:刷新令牌,訪問令牌過期后,使用刷新令牌重新獲取
// 路由文件 app.all('/oauth2/tokenHandler', oAuth2Server.tokenHandler()); // 獲取token
/** * 授權獲取token * @param req * @param res * @param next * @returns {Promise<*>} */ export function tokenHandler(type) { return async(req, res, next) => { const request = new Request(req); const response = new Response(res); try { const token = await oauth.token(request, response); res.locals.oauth = { token: token }; res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'accept, content-type, x-parse-application-id, x-parse-rest-api-key, x-parse-session-token'); next(); } catch (err) { log.error(new Error('tokenHandler Error'), err); return res.status(500).json(err); } }; }