OAuth2原理與LinkedIn的第三方分享實戰

OAuth是什麼

開放受權(OAuth)是一個開放標準,容許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。在全世界獲得普遍應用,目前的版本是2.0版。html

好比個人應用須要實如今領英上替用戶分享一個動態,可是隻有獲得用戶的受權才能在個人應用中調用領英的API進行分享操做,若是直接讓用戶把用戶名密碼傳到個人應用,確實能夠實現,可是有如下問題:git

  • 爲了後續的服務,個人應用會保存用戶的密碼,在個人應用中保存密碼很不安全
  • 用戶沒法限制個人應用中能進行哪些操做
  • 領英必需要有密碼登陸的功能,而單純的密碼登陸並不安全
  • 個人應用中還須要處理驗證碼這樣的額外身份驗證狀況
  • 用戶只有修改密碼,才能收回權限,但會讓全部得到用戶受權的第三方應用程序所有失效
  • 只要有一個第三方應用程序被破解,就會致使用戶密碼泄漏,以及全部被密碼保護的數據泄漏

OAuth就是爲了解決上面這些問題而誕生的。github

OAuth原理

+--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+
複製代碼
  • Client:客戶端,第三方應用,個人應用就是一個Client
  • Resource Owner :用戶
  • Authorization Server:受權服務器,即提供第三方登陸服務的服務器,如領英
  • Resource Server:服務提供商,一般和受權服務器屬於同一應用,如領英

根據上圖的信息,咱們能夠知道OAuth2的基本流程爲:golang

  1. 用戶調用第三方應用的功能,如分享,該功能須要使用用戶在服務提供商上的權限,因此第三方應用直接重定向到受權服務器的登陸、受權頁面等待用戶登陸、受權。
  2. 用戶登陸以後贊成受權,並返回一個憑證(code)給第三方應用
  3. 第三方應用經過第二步的憑證(code)向受權服務器請求受權
  4. 受權服務器驗證憑證(code)經過後,贊成受權,並返回一個服務訪問的憑證(Access Token)。
  5. 第三方應用經過第四步的憑證(Access Token)向服務提供商請求相關資源。
  6. 服務提供商驗證憑證(Access Token)經過後,將第三方應用請求的資源返回。

簡單來講,OAuth在"客戶端"與"服務提供商"之間,設置了一個受權層。"客戶端"不能直接登陸"服務提供商",只能登陸"受權層",以此將用戶與客戶端區分開來。"客戶端"登陸"受權層"所用的憑證(Access Token),與用戶的密碼不一樣。用戶能夠在登陸的時候,受權服務器指定受權層令牌的權限範圍和有效期。json

LinkedIn的OAuth2交互

下面以在個人應用上實如今領英中替用戶分享一個動態爲例子,真切地來體驗一把OAuth2。瀏覽器

交互圖以下,細節會在下面寫到:安全

配置LinkedIn應用程序

除了領英,其餘任意開發者平臺都須要咱們在平臺上註冊一個App才能給咱們調用平臺API的權限,也方便平臺對咱們的資質審覈以及調用管理。服務器

因此首先咱們得去領英開發者平臺去註冊一個App: www.linkedin.com/developer/a…cookie

  • Keys: 完成以後會獲得App相關的Key,這是在後續OAuth2交互時須要向領英提供身份驗證。
  • 權限:在用戶登陸時會給用戶展現出個人應用須要哪些權限,個人應用最後調用API的時候會檢測權限是否容許。
  • 回調API接口:這是在OAuth2交互時須要驗證的一個參數,領英只會與已識別爲可信終端的URL進行通訊。

獲取用戶受權憑證Code

用戶在個人應用中點擊【分享】按鈕,會發送一個GET請求到myApp/linkedin/auth/authorization,其中linkedin/auth/authorization是個人應用裏暴露出的一個專門用來進行驗證的接口。mvc

個人應用收到請求以後直接回復重定向到領英的受權頁面,而且重定向的url中必須包含領英實現的OAuth2的一些參數,以下:

其中state參數是防止csrf攻擊加入進來的,關於csrf以及本例中對csrf防範的實現會在文章最後一部分提到。

在go語言+Beego框架中的實現以下:

func (c *AuthorizationController) Get() {
	host := beego.AppConfig.String("host")
	state := util.Generate(10) // random string with length 10
	clientId := beego.AppConfig.String("linkedin_client_id") // 建立app獲得的key
	linkedinOauth2AuthorizationUrl := "https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=%s&redirect_uri=%s/linkedin/auth/callback&state=%s"
	uri := fmt.Sprintf(linkedinOauth2AuthorizationUrl, clientId, host, state)

	c.SetSession("_csrf_Token", state)
	c.Redirect(uri, 302)
}
複製代碼

瀏覽器跳轉到領英頁面等待受權

瀏覽器從上一步中根據重定向url跳轉到領英頁面,等待用戶登陸並受權,若是用戶已經登陸就直接跳轉,以下圖:

用戶若是點擊Cancel,或者請求因任何其餘緣由而失敗,則會將其重定向回redirect_uri的URL,並附加一些錯誤參數。

用戶若是點擊Allow,用戶批准您的應用程序訪問其成員數據並表明他們與LinkedIn進行交互,也會將其重定向回redirect_uri,並附加劇要參數code

經過Code拿到Token,並實現分享

redirect_uri就是個人應用中的callback接口,這個接口等待用戶傳遞一個容許受權的憑證code,在個人應用中就能夠拿這個code去領英中申請access token,之後就拿着這個access token去訪問領英中容許訪問的資源了

func (c *CallbackController) Get() {
	// csrf validation
	csrfToken := c.GetSession("_csrf_Token")
	if csrfToken != c.GetString("state") {
		c.Data["json"] = map[string]interface{}{
			"error":             c.GetString("csrf error"),
			"error_description": c.GetString("error_description")}
		c.ServeJSON()
		return
	}

	// user cancel authorization request or linkedin server error
	if c.GetString("error") != "" {
		c.Data["json"] = map[string]interface{}{
			"error":             c.GetString("error"),
			"error_description": c.GetString("error_description")}
		c.ServeJSON()
		return
	}

	// user accept authorization request
	code := c.GetString("code")
	if code != "" {
		// get access token by code
		accessToken := getAccessToken(code)
		// share by access token
		shareResponse := share(accessToken.AccessToken)
		c.Data["json"] = &shareResponse
		c.ServeJSON()
		return
	}
}
複製代碼

getAccessToken和share方法僅僅是發送請求到領英的REST API接口,實際上若是領英有golang的sdk的話,就不須要咱們本身使用golang的http包來本身封裝請求處理結果了,但惋惜領英並無。

最後給一個GIF圖,該圖包含了全部的流程展現,從點擊【分享】按鈕(調用localhost/linkedin/auth/authorization接口)開始:

CSRF

跨站請求僞造(Cross-site request forgery), 簡稱爲 CSRF,是 Web 應用中常見的一個安全問題。前面的連接也詳細講述了 CSRF 攻擊的實現方式。

當前防範 CSRF 的一種通用的方法,是對每個用戶都記錄一個沒法預知的 cookie 數據,而後要求全部提交的請求(POST/PUT/DELETE)中都必須帶有這個 cookie 數據。若是此數據不匹配 ,那麼這個請求就多是被僞造的。

咱們這裏防範CSRF的方法就是經過在用戶第一次點擊分享時(調用/linkedin/auth/authorization)在Session中存儲一個字符串:

func (c *AuthorizationController) Get() {
	...
	c.SetSession("_csrf_Token", state)
	...
}

複製代碼

當用戶瀏覽器拿到用戶的受權憑證code以後發送給個人應用時(調用/linkedin/auth/callback)檢測此時請求中的state與以前在Session中存儲的字符串是否相同。

func (c *CallbackController) Get() {
	...
	csrfToken := c.GetSession("_csrf_Token")
	if csrfToken != c.GetString("state") {
		c.Data["json"] = map[string]interface{}{"error": "xsrf error"}
		c.ServeJSON()
		return
	}
	...
}
複製代碼

若是相同則能判斷是用戶的請求,若是不一樣,則多是用戶點擊了其餘用戶僞造的一個連接發送的請求,從而能夠防止code被髮送到惡意網站上去

本文資源

代碼及部分圖片工程文件:github.com/xbox1994/OA…

參考

zh.wikipedia.org/wiki/開放受權
www.ruanyifeng.com/blog/2014/0…
developer.linkedin.com/docs/share-…
zhuanlan.zhihu.com/p/20913727
www.cnblogs.com/flashsun/p/…
beego.me/docs/mvc/co…

91Code-就要編碼,關注公衆號獲取更多內容!

在這裏插入圖片描述
相關文章
相關標籤/搜索