OAuth2.0

最近一直在負責開發公司的開放平臺相關工做,對接淘寶,阿里巴巴等開放平臺,同時也負責開發系統的開放平臺,在此稍做總結。本文只稍微分析聊一下受權碼模式,而且不嘗試解釋OAuth2.0參數爲何不是駝峯的……html

參考資料


RFC6794 理解OAuth 2.0git

使用場景


用戶登陸雲管店應用,此時沒有辦法直接登陸阿里巴巴應用查看數據,或者阿里巴巴數據還未通過處理,不是用戶的目標數據。數據庫

用戶登陸雲管店(假設該應用對接了阿里巴巴應用的接口)應用,查看本身門店當前的庫存數量,同時爲了更直觀的瞭解到當前阿里巴巴上掛的店鋪的庫存,雲管店要去訪問阿里巴巴接口拉取到該用戶在阿里巴巴的店鋪的倉庫數量,統計成報表。安全

若是不適用OAuth2.0雲管店應該如何讀取到阿里巴巴上的庫存數量?服務器

image.png

用戶提供阿里巴巴帳號密碼給雲管店雲管店經過帳號密碼便可讀取到庫存信息。那麼這麼作有帶來什麼隱患?架構

  • 阿里巴巴帳號密碼泄露給雲管店雲管店能夠任意獲取用戶在阿里巴巴上的數據
  • 雲管店數據庫若是泄露,也把阿里巴巴的帳號密碼等數據泄露出去
  • 爲了防止雲管店任意讀取數據,只能經過修改帳號密碼
  • ...

基於數據開放,且爲了保護用戶數據安全等諸多問題,OAuth2.0應運而生,併成爲當前最主流的解決方案。app

OAuth2.0 解決方案


OAuth2.0客戶端服務提供商之間,設置了一個受權訪問的屏障。客戶端沒法直接拿到服務提供商的登陸帳號密碼,也就沒法直接登陸服務提供商,只能請求受權服務提供商負載均衡

此時會要求用戶登陸資源提供商(該登陸服務由服務提供商提供,不會存在帳號密碼泄露等問題)。登陸後,受權服務提供商提示用戶確認受權後提供給客戶端一個token令牌。服務提供商根據令牌的時效和受權範圍,向客戶端開放數據。分佈式

image.png

OAuth2.0客戶端受權模式


  • 受權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

受權碼模式


受權碼模式(authorization code)是功能最完整、流程最嚴密的受權模式。它的特色就是經過客戶端的後臺服務器,與"服務提供商"的認證服務器進行互動。(本文只提到受權碼模式,其餘相關客戶端受權模式請參考上文的參考資料進行了解)微服務

image.png

流程解析

(A)用戶訪問客戶端,後者將前者導向認證服務器。 (B)用戶選擇是否給予客戶端受權。 (C)假設用戶給予受權,認證服務器將用戶導向客戶端事先指定的"重定向URI"(redirection URI),同時附上一個受權碼。 (D)客戶端收到受權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客戶端的後臺的服務器上完成的,對用戶不可見。 (E)認證服務器覈對了受權碼和重定向URI,確認無誤後,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)。

A步驟中,客戶端申請認證的URI,包含如下參數:

  • response_type:表示受權類型,必選項,此處的值固定爲"code"
  • client_id:表示客戶端的ID,必選項
  • redirect_uri:表示重定向URI,可選項
  • scope:表示申請的權限範圍,可選項
  • state:表示客戶端的當前狀態,能夠指定任意值,認證服務器會原封不動地返回這個值。

C步驟中,服務器迴應客戶端的URI,包含如下參數:

  • code:表示受權碼,必選項。該碼的有效期應該很短,一般設爲10分鐘,客戶端只能使用該碼一次,不然會被受權服務器拒絕。該碼與客戶端ID和重定向URI,是一一對應關係。
  • state:若是客戶端的請求中包含這個參數,認證服務器的迴應也必須如出一轍包含這個參數。

D步驟中,客戶端向認證服務器申請令牌的HTTP請求,包含如下參數:

  • grant_type:表示使用的受權模式,必選項,此處的值固定爲"authorization_code"。
  • code:表示上一步得到的受權碼,必選項。
  • redirect_uri:表示重定向URI,必選項,且必須與A步驟中的該參數值保持一致。
  • client_id:表示客戶端ID,必選項。

E步驟中,認證服務器發送的HTTP回覆,包含如下參數:

  • access_token:表示訪問令牌,必選項。
  • token_type:表示令牌類型,該值大小寫不敏感,必選項,能夠是bearer類型或mac類型。
  • expires_in:表示過時時間,單位爲秒。若是省略該參數,必須其餘方式設置過時時間。
  • refresh_token:表示更新令牌,用來獲取下一次的訪問令牌,可選項。
  • scope:表示權限範圍,若是與客戶端申請的範圍一致,此項可省略。

基於規範,動手實現一個簡易版的受權碼模式


對應於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校驗合法性。

image.png

受權服務獨立一個應用,功能簡單,輕量. 資源服務可能因爲訪問量較大,須要部署多臺服務,經過負載均衡來保證服務穩定。

當客戶端受權完成併成功拿到token以後便可用它來訪問資源服務,拉取數據。那麼此時就須要校驗token的合法性,那麼誰來校驗token纔是最合適的呢?

資源服務提供者進行token校驗

資源服務提供token合法性校驗

  • 資源服務須要校驗token的合法性,相對複雜
  • 受理了校驗token合法性的業務,不能爲其餘應用提供服務,接口受制。

網管中心進行token校驗

網管中心是掌管一切請求的入口,在這一層作token校驗也是極爲合理的。

  • 就如同須要校驗請求是否登陸同樣,在網管中心校驗token
  • 接口不受理校驗token合法性的業務,接口能夠做爲其餘服務提供者。
  • 實現相對複雜

受權服務進行token校驗

受權服務提供token合法性校驗,經過feign將請求再轉發到資源服務

  • 把全部與受權相關的處理都統一在一個應用處理。
  • 受權服務的壓力甚至比資源服務壓力更大,由於全部請求全都要通過受權服務,因此受權服務也須要多臺部署。
  • 接口不受理校驗token合法性業務,接口能夠做爲其餘服務提供者。

從架構上來講,更加推薦使用網管中心進行token校驗,業務方接口方可複用。受權服務進行token檢驗亦有其優點,業務方接口亦可複用,可是服務壓力大。

後記

OAuth2.0 目前已經被各大互聯網公司所使用,足以證實它的優秀與不凡。

相關文章
相關標籤/搜索