最近一直在負責開發公司的開放平臺相關工做,對接淘寶,阿里巴巴等開放平臺,同時也負責開發系統的開放平臺,在此稍做總結。本文只稍微分析聊一下受權碼模式,而且不嘗試解釋OAuth2.0參數爲何不是駝峯的……html
用戶登陸雲管店
應用,此時沒有辦法直接登陸阿里巴巴
應用查看數據,或者阿里巴巴
數據還未通過處理,不是用戶的目標數據。數據庫
用戶登陸雲管店
(假設該應用對接了阿里巴巴應用的接口)應用,查看本身門店當前的庫存數量,同時爲了更直觀的瞭解到當前阿里巴巴
上掛的店鋪的庫存,雲管店
要去訪問阿里巴巴
接口拉取到該用戶在阿里巴巴
的店鋪的倉庫數量,統計成報表。安全
若是不適用OAuth2.0
,雲管店
應該如何讀取到阿里巴巴
上的庫存數量?服務器
用戶提供
阿里巴巴
帳號密碼給雲管店
,雲管店
經過帳號密碼便可讀取到庫存信息。那麼這麼作有帶來什麼隱患?架構
阿里巴巴
帳號密碼泄露給雲管店
,雲管店
能夠任意獲取用戶在阿里巴巴
上的數據雲管店
數據庫若是泄露,也把阿里巴巴
的帳號密碼等數據泄露出去雲管店
任意讀取數據,只能經過修改帳號密碼基於數據開放,且爲了保護用戶數據安全等諸多問題,OAuth2.0應運而生,併成爲當前最主流的解決方案。app
OAuth2.0
在客戶端
與服務提供商
之間,設置了一個受權訪問的屏障。客戶端
沒法直接拿到服務提供商
的登陸帳號密碼,也就沒法直接登陸服務提供商
,只能請求受權服務提供商
。負載均衡
此時會要求用戶登陸資源提供商
(該登陸服務由服務提供商
提供,不會存在帳號密碼泄露等問題)。登陸後,受權服務提供商
提示用戶確認受權後提供給客戶端
一個token
令牌。服務提供商
根據令牌的時效和受權範圍,向客戶端
開放數據。分佈式
受權碼模式(authorization code)是功能最完整、流程最嚴密的受權模式。它的特色就是經過客戶端的後臺服務器,與"服務提供商"的認證服務器進行互動。(本文只提到受權碼模式,其餘相關客戶端受權模式請參考上文的參考資料進行了解)微服務
流程解析
(A)用戶訪問客戶端,後者將前者導向認證服務器。 (B)用戶選擇是否給予客戶端受權。 (C)假設用戶給予受權,認證服務器將用戶導向客戶端事先指定的"重定向URI"(redirection URI),同時附上一個受權碼。 (D)客戶端收到受權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客戶端的後臺的服務器上完成的,對用戶不可見。 (E)認證服務器覈對了受權碼和重定向URI,確認無誤後,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)。
A步驟中,客戶端申請認證的URI,包含如下參數:
C步驟中,服務器迴應客戶端的URI,包含如下參數:
D步驟中,客戶端向認證服務器申請令牌的HTTP請求,包含如下參數:
E步驟中,認證服務器發送的HTTP回覆,包含如下參數:
對應於A步驟,客戶端發起受權請求(該請求能夠要求登陸,用戶訪問該請求須要登陸)。受權參數須要參照OAuth2.0
規範,最好是相應的參數名稱都按照規範來。
@RequestMapping(value = "/authorize") public String authorize(ModelMap modelMap, AuthorizeDTO authorizeDTO) { // 若是是受權碼模式 if(GrantTypeEnum.AUTHORIZATION_CODE.getValue().equals(authorizeDTO.getResponse_type())) { // 檢驗客戶信息 if(!StoreFactory.getClientStore().isContainsClientId(authorizeDTO.getClient_id())) { ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_CLIENT_ID); return returnErrorPage(); } // 檢驗重定向地址 if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeDTO.getClient_id(), authorizeDTO.getRedirect_uri())) { ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_REDIRECT_URI); return returnErrorPage(); } modelMap.put("client_id", authorizeDTO.getClient_id()); modelMap.put("redirect_uri", authorizeDTO.getRedirect_uri()); modelMap.put("state", authorizeDTO.getState()); } return "/auth"; }
對應步驟C,確認受權後能夠獲取到相應的code與state等參數,附着在回調地址中,且該回調地址必須與申請資質時填寫的回調的地址(申請資質須要客戶端應用向服務提供商申請,由服務提供商頒發相應的key與secret)
@RequestMapping(value = "/confirm") public String accessConfirm(ModelMap modelMap, AuthorizeDTO authorizeDTO) { // 檢驗客戶信息 if(!StoreFactory.getClientStore().isContainsClientId(authorizeDTO.getClient_id())) { ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_CLIENT_ID); return returnErrorPage(); } // 檢驗重定向地址 if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeDTO.getClient_id(), authorizeDTO.getRedirect_uri())) { ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_REDIRECT_URI); return returnErrorPage(); } // 根據填寫的回調地址回調回去 return "redirect:" + authorizeDTO.getRedirect_uri()+"?code="+StoreFactory.getCodeStore().createUUIDCode(authorizeDTO.getClient_id()) +"&state="+authorizeDTO.getState(); }
對應步驟E,使用獲取到的code去換取token,或者使用舊的refresh_token去獲取新的token
@RequestMapping(value = "/token") @ResponseBody public ResultObject accessToken(ModelMap modelMap, AuthorizeTokenDTO authorizeTokenDTO) { // 檢驗客戶信息 if(!StoreFactory.getClientStore().isConatinsClient(authorizeTokenDTO.getClient_id(), authorizeTokenDTO.getClient_secret())) { return ResultMessage.ERROR_CLIENT_ID.getResultObject(); } // 檢驗重定向地址 if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeTokenDTO.getClient_id(), authorizeTokenDTO.getRedirect_uri())) { return ResultMessage.ERROR_REDIRECT_URI.getResultObject(); } // 檢驗code if(!StoreFactory.getCodeStore().isRightCode(authorizeTokenDTO.getCode(), authorizeTokenDTO.getClient_id())) { return ResultMessage.ERROR_CODE.getResultObject(); } // 生成token if(GrantTypeEnum.AUTHORIZATION_CODE.equals(authorizeTokenDTO.getGrant_type())) { // 也能夠根據redirect_uri 回調回去 // 也能夠將返回值包裝成Josn返回 // return ResultMessage.SUCCESS.getResultObject(StoreFactory.getTokenStore().createUUIDToken(authorizeTokenDTO.getClient_id())); } // 刷新token if(GrantTypeEnum.REFRESH_TOKEN.equals(authorizeTokenDTO.getGrant_type())) { // 拿到refreshToken 並檢驗刷新 // 這裏沒有作實現,可是原理一致 return ResultMessage.SUCCESS.getResultObject(StoreFactory.getTokenStore().createUUIDToken(authorizeTokenDTO.getClient_id())); } return ResultMessage.ERROR_GRANT_TYPE.getResultObject(); }
如此簡單即可以實現一個最簡易的受權碼模式的服務。麻雀雖小,卻也五臟俱全,不能直接用於真實生產環境,可是對於理解OAuth2.0的受權過程卻也足以。
代碼地址:https://gitee.com/linweifeng/OAuth/tree/master
若是是單機應用,咱們的受權服務,資源服務(開放的接口)都是能夠統一放在一個應用上,那麼實現天然是很是簡單,經過攔截器/自定義註解實現AOP均可以作到很是完美,代碼寫起來也很6很順手。
可是若是是分佈式環境,好比如今最流行的微服務架構
就須要考慮的問題比較多,好比token
校驗合法性。
受權服務
獨立一個應用,功能簡單,輕量. 資源服務
可能因爲訪問量較大,須要部署多臺服務,經過負載均衡來保證服務穩定。
當客戶端受權完成併成功拿到token
以後便可用它來訪問資源服務,拉取數據。那麼此時就須要校驗token
的合法性,那麼誰來校驗token
纔是最合適的呢?
資源服務
提供token
合法性校驗
token
的合法性,相對複雜token
合法性的業務,不能爲其餘應用提供服務,接口受制。網管中心
是掌管一切請求的入口,在這一層作token
校驗也是極爲合理的。
token
token
合法性的業務,接口能夠做爲其餘服務提供者。受權服務
提供token
合法性校驗,經過feign
將請求再轉發到資源服務
資源服務
壓力更大,由於全部請求全都要通過受權服務
,因此受權服務
也須要多臺部署。token
合法性業務,接口能夠做爲其餘服務提供者。從架構上來講,更加推薦使用網管中心進行token校驗,業務方接口方可複用。受權服務進行token檢驗亦有其優點,業務方接口亦可複用,可是服務壓力大。
OAuth2.0 目前已經被各大互聯網公司所使用,足以證實它的優秀與不凡。