最近一直在負(fù)責(zé)開發(fā)公司的開放平臺(tái)相關(guān)工作奈揍,對(duì)接淘寶,阿里巴巴等開放平臺(tái)赋续,同時(shí)也負(fù)責(zé)開發(fā)系統(tǒng)的開放平臺(tái)男翰,在此稍作總結(jié)。本文只稍微分析聊一下授權(quán)碼模式纽乱,并且不嘗試解釋OAuth2.0參數(shù)為什么不是駝峰的……
參考資料
使用場(chǎng)景
用戶登錄云管店
應(yīng)用蛾绎,此時(shí)沒(méi)有辦法直接登錄阿里巴巴
應(yīng)用查看數(shù)據(jù),或者阿里巴巴
數(shù)據(jù)還未經(jīng)過(guò)處理鸦列,不是用戶的目標(biāo)數(shù)據(jù)租冠。
用戶登錄云管店
(假設(shè)該應(yīng)用對(duì)接了阿里巴巴應(yīng)用的接口)應(yīng)用,查看自己門店當(dāng)前的庫(kù)存數(shù)量薯嗤,同時(shí)為了更直觀的了解到當(dāng)前阿里巴巴
上掛的店鋪的庫(kù)存顽爹,云管店
要去訪問(wèn)阿里巴巴
接口拉取到該用戶在阿里巴巴
的店鋪的倉(cāng)庫(kù)數(shù)量,統(tǒng)計(jì)成報(bào)表骆姐。
如果不適用OAuth2.0
镜粤,云管店
應(yīng)該如何讀取到阿里巴巴
上的庫(kù)存數(shù)量捏题?
用戶提供
阿里巴巴
賬號(hào)密碼給云管店
,云管店
通過(guò)賬號(hào)密碼即可讀取到庫(kù)存信息肉渴。那么這么做有帶來(lái)什么隱患公荧?
-
阿里巴巴
賬號(hào)密碼泄露給云管店
,云管店
可以任意獲取用戶在阿里巴巴
上的數(shù)據(jù) -
云管店
數(shù)據(jù)庫(kù)如果泄露同规,也把阿里巴巴
的賬號(hào)密碼等數(shù)據(jù)泄露出去 - 為了防止
云管店
任意讀取數(shù)據(jù)稚矿,只能通過(guò)修改賬號(hào)密碼 - ...
基于數(shù)據(jù)開放,且為了保護(hù)用戶數(shù)據(jù)安全等諸多問(wèn)題捻浦,OAuth2.0應(yīng)運(yùn)而生,并成為當(dāng)前最主流的解決方案桥爽。
OAuth2.0 解決方案
OAuth2.0
在客戶端
與服務(wù)提供商
之間朱灿,設(shè)置了一個(gè)授權(quán)訪問(wèn)的屏障。客戶端
無(wú)法直接拿到服務(wù)提供商
的登錄賬號(hào)密碼钠四,也就無(wú)法直接登錄服務(wù)提供商
盗扒,只能請(qǐng)求授權(quán)服務(wù)提供商
。
此時(shí)會(huì)要求用戶登錄資源提供商
(該登錄服務(wù)由服務(wù)提供商
提供缀去,不會(huì)存在賬號(hào)密碼泄露等問(wèn)題)侣灶。登錄后,授權(quán)服務(wù)提供商
提示用戶確認(rèn)授權(quán)后提供給客戶端
一個(gè)token
令牌缕碎。服務(wù)提供商
根據(jù)令牌的時(shí)效和授權(quán)范圍褥影,向客戶端
開放數(shù)據(jù)。
OAuth2.0客戶端授權(quán)模式
- 授權(quán)碼模式(authorization code)
- 簡(jiǎn)化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
授權(quán)碼模式
授權(quán)碼模式(authorization code)是功能最完整咏雌、流程最嚴(yán)密的授權(quán)模式凡怎。它的特點(diǎn)就是通過(guò)客戶端的后臺(tái)服務(wù)器,與"服務(wù)提供商"的認(rèn)證服務(wù)器進(jìn)行互動(dòng)赊抖。(本文只提到授權(quán)碼模式统倒,其他相關(guān)客戶端授權(quán)模式請(qǐng)參考上文的參考資料進(jìn)行了解)
流程解析
(A)用戶訪問(wèn)客戶端,后者將前者導(dǎo)向認(rèn)證服務(wù)器氛雪。
(B)用戶選擇是否給予客戶端授權(quán)房匆。
(C)假設(shè)用戶給予授權(quán),認(rèn)證服務(wù)器將用戶導(dǎo)向客戶端事先指定的"重定向URI"(redirection URI)报亩,同時(shí)附上一個(gè)授權(quán)碼浴鸿。
(D)客戶端收到授權(quán)碼,附上早先的"重定向URI"捆昏,向認(rèn)證服務(wù)器申請(qǐng)令牌赚楚。這一步是在客戶端的后臺(tái)的服務(wù)器上完成的,對(duì)用戶不可見(jiàn)骗卜。
(E)認(rèn)證服務(wù)器核對(duì)了授權(quán)碼和重定向URI宠页,確認(rèn)無(wú)誤后左胞,向客戶端發(fā)送訪問(wèn)令牌(access token)和更新令牌(refresh token)。
A步驟中举户,客戶端申請(qǐng)認(rèn)證的URI烤宙,包含以下參數(shù):
- response_type:表示授權(quán)類型,必選項(xiàng)俭嘁,此處的值固定為"code"
- client_id:表示客戶端的ID躺枕,必選項(xiàng)
- redirect_uri:表示重定向URI,可選項(xiàng)
- scope:表示申請(qǐng)的權(quán)限范圍供填,可選項(xiàng)
- state:表示客戶端的當(dāng)前狀態(tài)拐云,可以指定任意值,認(rèn)證服務(wù)器會(huì)原封不動(dòng)地返回這個(gè)值近她。
C步驟中叉瘩,服務(wù)器回應(yīng)客戶端的URI,包含以下參數(shù):
- code:表示授權(quán)碼粘捎,必選項(xiàng)薇缅。該碼的有效期應(yīng)該很短,通常設(shè)為10分鐘攒磨,客戶端只能使用該碼一次泳桦,否則會(huì)被授權(quán)服務(wù)器拒絕。該碼與客戶端ID和重定向URI娩缰,是一一對(duì)應(yīng)關(guān)系灸撰。
- state:如果客戶端的請(qǐng)求中包含這個(gè)參數(shù),認(rèn)證服務(wù)器的回應(yīng)也必須一模一樣包含這個(gè)參數(shù)拼坎。
D步驟中梧奢,客戶端向認(rèn)證服務(wù)器申請(qǐng)令牌的HTTP請(qǐng)求,包含以下參數(shù):
- grant_type:表示使用的授權(quán)模式演痒,必選項(xiàng)亲轨,此處的值固定為"authorization_code"。
- code:表示上一步獲得的授權(quán)碼鸟顺,必選項(xiàng)惦蚊。
- redirect_uri:表示重定向URI,必選項(xiàng)讯嫂,且必須與A步驟中的該參數(shù)值保持一致蹦锋。
- client_id:表示客戶端ID,必選項(xiàng)欧芽。
E步驟中莉掂,認(rèn)證服務(wù)器發(fā)送的HTTP回復(fù),包含以下參數(shù):
- access_token:表示訪問(wèn)令牌千扔,必選項(xiàng)憎妙。
- token_type:表示令牌類型库正,該值大小寫不敏感,必選項(xiàng)厘唾,可以是bearer類型或mac類型褥符。
- expires_in:表示過(guò)期時(shí)間,單位為秒抚垃。如果省略該參數(shù)喷楣,必須其他方式設(shè)置過(guò)期時(shí)間。
- refresh_token:表示更新令牌鹤树,用來(lái)獲取下一次的訪問(wèn)令牌铣焊,可選項(xiàng)。
- scope:表示權(quán)限范圍罕伯,如果與客戶端申請(qǐng)的范圍一致粗截,此項(xiàng)可省略。
基于規(guī)范捣炬,動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的授權(quán)碼模式
對(duì)應(yīng)于A步驟,客戶端發(fā)起授權(quán)請(qǐng)求(該請(qǐng)求可以要求登錄绽榛,用戶訪問(wèn)該請(qǐng)求需要登錄)湿酸。授權(quán)參數(shù)需要參照OAuth2.0
規(guī)范,最好是相應(yīng)的參數(shù)名稱都按照規(guī)范來(lái)灭美。
@RequestMapping(value = "/authorize")
public String authorize(ModelMap modelMap, AuthorizeDTO authorizeDTO) {
// 如果是授權(quán)碼模式
if(GrantTypeEnum.AUTHORIZATION_CODE.getValue().equals(authorizeDTO.getResponse_type())) {
// 檢驗(yàn)客戶信息
if(!StoreFactory.getClientStore().isContainsClientId(authorizeDTO.getClient_id())) {
ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_CLIENT_ID);
return returnErrorPage();
}
// 檢驗(yàn)重定向地址
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";
}
對(duì)應(yīng)步驟C推溃,確認(rèn)授權(quán)后可以獲取到相應(yīng)的code與state等參數(shù),附著在回調(diào)地址中届腐,且該回調(diào)地址必須與申請(qǐng)資質(zhì)時(shí)填寫的回調(diào)的地址(申請(qǐng)資質(zhì)需要客戶端應(yīng)用向服務(wù)提供商申請(qǐng)铁坎,由服務(wù)提供商頒發(fā)相應(yīng)的key與secret)
@RequestMapping(value = "/confirm")
public String accessConfirm(ModelMap modelMap, AuthorizeDTO authorizeDTO) {
// 檢驗(yàn)客戶信息
if(!StoreFactory.getClientStore().isContainsClientId(authorizeDTO.getClient_id())) {
ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_CLIENT_ID);
return returnErrorPage();
}
// 檢驗(yàn)重定向地址
if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeDTO.getClient_id(), authorizeDTO.getRedirect_uri())) {
ModelMapUtil.setMessage(modelMap, ResultMessage.ERROR_REDIRECT_URI);
return returnErrorPage();
}
// 根據(jù)填寫的回調(diào)地址回調(diào)回去
return "redirect:" + authorizeDTO.getRedirect_uri()+"?code="+StoreFactory.getCodeStore().createUUIDCode(authorizeDTO.getClient_id())
+"&state="+authorizeDTO.getState();
}
對(duì)應(yīng)步驟E,使用獲取到的code去換取token犁苏,或者使用舊的refresh_token去獲取新的token
@RequestMapping(value = "/token")
@ResponseBody
public ResultObject accessToken(ModelMap modelMap, AuthorizeTokenDTO authorizeTokenDTO) {
// 檢驗(yàn)客戶信息
if(!StoreFactory.getClientStore().isConatinsClient(authorizeTokenDTO.getClient_id(), authorizeTokenDTO.getClient_secret())) {
return ResultMessage.ERROR_CLIENT_ID.getResultObject();
}
// 檢驗(yàn)重定向地址
if(!StoreFactory.getClientStore().isContainsRedirectUri(authorizeTokenDTO.getClient_id(), authorizeTokenDTO.getRedirect_uri())) {
return ResultMessage.ERROR_REDIRECT_URI.getResultObject();
}
// 檢驗(yàn)code
if(!StoreFactory.getCodeStore().isRightCode(authorizeTokenDTO.getCode(), authorizeTokenDTO.getClient_id())) {
return ResultMessage.ERROR_CODE.getResultObject();
}
// 生成token
if(GrantTypeEnum.AUTHORIZATION_CODE.equals(authorizeTokenDTO.getGrant_type())) {
// 也可以根據(jù)redirect_uri 回調(diào)回去
// 也可以將返回值包裝成Josn返回
//
return ResultMessage.SUCCESS.getResultObject(StoreFactory.getTokenStore().createUUIDToken(authorizeTokenDTO.getClient_id()));
}
// 刷新token
if(GrantTypeEnum.REFRESH_TOKEN.equals(authorizeTokenDTO.getGrant_type())) {
// 拿到refreshToken 并檢驗(yàn)刷新
// 這里沒(méi)有做實(shí)現(xiàn)硬萍,但是原理一致
return ResultMessage.SUCCESS.getResultObject(StoreFactory.getTokenStore().createUUIDToken(authorizeTokenDTO.getClient_id()));
}
return ResultMessage.ERROR_GRANT_TYPE.getResultObject();
}
如此簡(jiǎn)單便可以實(shí)現(xiàn)一個(gè)最簡(jiǎn)易的授權(quán)碼模式的服務(wù)。麻雀雖小围详,卻也五臟俱全朴乖,不能直接用于真實(shí)生產(chǎn)環(huán)境,但是對(duì)于理解OAuth2.0的授權(quán)過(guò)程卻也足以助赞。
代碼地址:https://gitee.com/linweifeng/OAuth/tree/master
分布式環(huán)境
如果是單機(jī)應(yīng)用买羞,我們的授權(quán)服務(wù),資源服務(wù)(開放的接口)都是可以統(tǒng)一放在一個(gè)應(yīng)用上雹食,那么實(shí)現(xiàn)自然是非常簡(jiǎn)單畜普,通過(guò)攔截器/自定義注解實(shí)現(xiàn)AOP都可以做到非常完美,代碼寫起來(lái)也很6很順手群叶。
但是如果是分布式環(huán)境吃挑,比如現(xiàn)在最流行的微服務(wù)架構(gòu)
就需要考慮的問(wèn)題比較多钝荡,比如token
校驗(yàn)合法性。
授權(quán)服務(wù)
獨(dú)立一個(gè)應(yīng)用儒鹿,功能簡(jiǎn)單化撕,輕量.
資源服務(wù)
可能由于訪問(wèn)量較大,需要部署多臺(tái)服務(wù)约炎,通過(guò)負(fù)載均衡來(lái)保證服務(wù)穩(wěn)定植阴。
當(dāng)客戶端授權(quán)完成并成功拿到token
之后即可用它來(lái)訪問(wèn)資源服務(wù),拉取數(shù)據(jù)圾浅。那么此時(shí)就需要校驗(yàn)token
的合法性掠手,那么誰(shuí)來(lái)校驗(yàn)token
才是最合適的呢?
資源服務(wù)提供者進(jìn)行token校驗(yàn)
資源服務(wù)
提供token
合法性校驗(yàn)
- 資源服務(wù)需要校驗(yàn)
token
的合法性狸捕,相對(duì)復(fù)雜 - 受理了校驗(yàn)
token
合法性的業(yè)務(wù)喷鸽,不能為其他應(yīng)用提供服務(wù),接口受制灸拍。
網(wǎng)管中心進(jìn)行token校驗(yàn)
網(wǎng)管中心
是掌管一切請(qǐng)求的入口做祝,在這一層做token
校驗(yàn)也是極為合理的。
- 就如同需要校驗(yàn)請(qǐng)求是否登錄一樣鸡岗,在網(wǎng)管中心校驗(yàn)
token
- 接口不受理校驗(yàn)
token
合法性的業(yè)務(wù)混槐,接口可以作為其他服務(wù)提供者。 - 實(shí)現(xiàn)相對(duì)復(fù)雜
授權(quán)服務(wù)進(jìn)行token校驗(yàn)
授權(quán)服務(wù)
提供token
合法性校驗(yàn)轩性,通過(guò)feign
將請(qǐng)求再轉(zhuǎn)發(fā)到資源服務(wù)
- 把所有與授權(quán)相關(guān)的處理都統(tǒng)一在一個(gè)應(yīng)用處理声登。
- 授權(quán)服務(wù)的壓力甚至比
資源服務(wù)
壓力更大,因?yàn)樗姓?qǐng)求全都要經(jīng)過(guò)授權(quán)服務(wù)
揣苏,所以授權(quán)服務(wù)
也需要多臺(tái)部署悯嗓。 - 接口不受理校驗(yàn)
token
合法性業(yè)務(wù),接口可以作為其他服務(wù)提供者卸察。
從架構(gòu)上來(lái)說(shuō)脯厨,更加推薦使用網(wǎng)管中心進(jìn)行token校驗(yàn),業(yè)務(wù)方接口方可復(fù)用坑质。授權(quán)服務(wù)進(jìn)行token檢驗(yàn)亦有其優(yōu)勢(shì)俄认,業(yè)務(wù)方接口亦可復(fù)用,但是服務(wù)壓力大洪乍。
后記
OAuth2.0 目前已經(jīng)被各大互聯(lián)網(wǎng)公司所使用眯杏,足以證明它的優(yōu)秀與不凡。