OAuth2.0授權(quán)

最近一直在負(fù)責(zé)開發(fā)公司的開放平臺(tái)相關(guān)工作奈揍,對(duì)接淘寶,阿里巴巴等開放平臺(tái)赋续,同時(shí)也負(fù)責(zé)開發(fā)系統(tǒng)的開放平臺(tái)男翰,在此稍作總結(jié)。本文只稍微分析聊一下授權(quán)碼模式纽乱,并且不嘗試解釋OAuth2.0參數(shù)為什么不是駝峰的……

參考資料


RFC6794
理解OAuth 2.0

使用場(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ù)量捏题?

image.png

用戶提供阿里巴巴賬號(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ù)。

image.png

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)行了解)

image.png

流程解析

(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)合法性。

image.png

授權(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)秀與不凡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壳澳,一起剝皮案震驚了整個(gè)濱河市岂贩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖萎津,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卸伞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锉屈,警方通過(guò)查閱死者的電腦和手機(jī)荤傲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颈渊,“玉大人遂黍,你說(shuō)我怎么就攤上這事】∷裕” “怎么了雾家?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绍豁。 經(jīng)常有香客問(wèn)我芯咧,道長(zhǎng),這世上最難降的妖魔是什么竹揍? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任敬飒,我火速辦了婚禮,結(jié)果婚禮上芬位,老公的妹妹穿的比我還像新娘无拗。我一直安慰自己,他們只是感情好晶衷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阴孟,像睡著了一般晌纫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上永丝,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天锹漱,我揣著相機(jī)與錄音,去河邊找鬼慕嚷。 笑死哥牍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喝检。 我是一名探鬼主播嗅辣,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挠说!你這毒婦竟也來(lái)了澡谭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤损俭,失蹤者是張志新(化名)和其女友劉穎蛙奖,沒(méi)想到半個(gè)月后潘酗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雁仲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年仔夺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攒砖。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缸兔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祭衩,到底是詐尸還是另有隱情灶体,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布掐暮,位于F島的核電站蝎抽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏路克。R本人自食惡果不足惜樟结,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望精算。 院中可真熱鬧瓢宦,春花似錦、人聲如沸灰羽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)廉嚼。三九已至玫镐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怠噪,已是汗流浹背恐似。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留傍念,地道東北人矫夷。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像憋槐,于是被迫代替她去往敵國(guó)和親双藕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1阳仔、什么是OAuth2.0協(xié)議蔓彩? OAuth2.0(Open Authorization 2.0,開放授權(quán))協(xié)議是...
    中峰閱讀 6,343評(píng)論 5 6
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)赤嚼,斷路器旷赖,智...
    卡卡羅2017閱讀 134,633評(píng)論 18 139
  • 什么是三方授權(quán)? 第三方授權(quán)就是,委托第三方來(lái)對(duì)既定的用戶進(jìn)行鑒定更卒,鑒定成功之后等孵,下發(fā)信任憑證,信任憑證和用戶掛鉤...
    一只小哈閱讀 32,528評(píng)論 2 21
  • 過(guò)程都是一樣的: 第三方發(fā)起微信授權(quán)登錄請(qǐng)求上枕,微信用戶允許授權(quán)第三方應(yīng)用后咐熙,微信會(huì)拉起應(yīng)用或重定向到第三方網(wǎng)站,并...
    米刀靈閱讀 2,938評(píng)論 0 1
  • 原文地址:http://www.sanjinbest.com/blog/b6ec839d56c04ca387b95...
    木子小三金閱讀 8,547評(píng)論 0 37