OAuth(開放受權)是一個開放標準,容許用戶受權第三方移動應用訪問他們存儲在另外的服務提供者上的信息,而不須要將用戶名和密碼提供給第三方移動應用或分享他們數據的全部內容,OAuth2.0是OAuth協議的延續版本,但不向後兼容OAuth 1.0即徹底廢止了OAuth1.0。html
OAuth在"客戶端"與"服務提供商"之間,設置了一個受權層(authorization layer)。"客戶端"不能直接登陸"服務提供商",只能登陸受權層,以此將用戶與客戶端區分開來。"客戶端"登陸受權層所用的令牌(token),與用戶的密碼不一樣。用戶能夠在登陸的時候,指定受權層令牌的權限範圍和有效期。"客戶端"登陸受權層之後,"服務提供商"根據令牌的權限範圍和有效期,向"客戶端"開放用戶儲存的資料。前端
客戶端必須獲得用戶的受權(authorization grant),才能得到令牌(access token)。OAuth 2.0定義了四種受權方式。json
具體關於OAuth2的介紹,能夠參考阮一峯的博客 《理解OAuth 2.0》。後端
如今我已經基於Spring OAuth2 已經搭建好了這樣一個受權層,本文稱之爲認證中心。那麼寫本文檔的目的,就是指導你們如何將第三方的系統接入這個的認證中心,融入統一的單點登陸環境。我這裏簡單畫了一個圖。瀏覽器
如圖所示,新的第三方平臺(如:A平臺和B平臺),登陸和註銷的操做都跳轉到「OAuth2認證中心」的域名下進行,因爲認證經過後會生成Cookie,因此A平臺登陸後,同一單點登陸的B平臺、C平臺都無需再次登陸。接入單點登陸的第三方平臺都會被授予客戶端的clientId和clientSecret,經過「受權碼模式」登陸經過後獲取受權碼,而後第三方平臺就能夠拿受權碼經過API來獲取令牌(access token)。服務器
咱們經過一個案例,講解如何將第三方平臺接入OAuth2的單點登陸,這裏以紙蜂平臺爲例。下圖是紙蜂平臺接入OAuth2單點登陸的時序圖。app
因爲咱們選用的是受權碼模式,當新的客戶端接入單點登陸時,都須要在認證中心註冊對應的 clientId和clientSecret。而客戶端只須要保存 clientId和對兩者加密後的 Authorization值,若是紙蜂的clientId爲client1加密
咱們OAuth2簽發的令牌是經過Jwt來生成token的,每一個客戶端能夠自定義jwt token的過時時間,而後註冊在認證中心。url
紙蜂的前端是由路由守衛控制,路由守衛的過濾規則是判斷LocalStorage中是否有access_token,若是有access_token,而且未過時則頁面路由放行。spa
當前端路由守衛在LocalStorage中獲取不到access_token時,則跳轉至認證中心的受權登陸地址:{認證中心域名}/uaa/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://第三方/login/loading
該受權登陸頁面會引導url調整到登陸頁 - {認證中心域名}/uaa/login ,在登陸成功後會跳轉會重定向的地址,並在url param中帶上受權碼,如:http://第三方/login/loading?c... 。該頁面用於經過受權碼獲取 access_token和refresh_token,並存入LocalStorage。
經過受權碼獲取token的接口(POST)調用方式爲:
Header: Authorization:對clientId和clientSecret 經過Basic Auth 加密後的值 Content-Type:application/x-www-form-urlencoded Body: grant_type:authorization_code code:受權碼 redirect_uri:http://第三方/login/loading 返回結果示例: { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTYzOTg1MjU2LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYzg4YjM5ZjEtYTE2Yy00OGI3LTg4N2MtMjM3MTc5NDI3MjRhIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.Gmt8xFMqbRcx96rmlhg8AZhrMxDRGorVjK8wV_AEox4", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJhdGkiOiJjODhiMzlmMS1hMTZjLTQ4YjctODg3Yy0yMzcxNzk0MjcyNGEiLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTY2NTc2NTYxLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMjNjYzIyY2EtNmI2OC00ZmJkLTg1OGItZjcxNzVkNzAxOTgyIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.DY_GvNRS7mJP8FrPorffoqvTokU3udnlYnnFcWv0x4g", "expires_in": 599, "jti": "c88b39f1-a16c-48b7-887c-23717942724a" }
路由守衛雖然在LocalStorage中檢測到access_token,但當檢測到該jwt 的token已過時,則須要拿 refresh_token經過調用接口 - {認證中心域名}/uaa/oauth/token ,來獲取最新的access_token和refresh_token,並在LocalStorage中替換。
刷新token的接口(POST)調用方式爲:
Header: Authorization:clientId和clientSecret加密後的值 Content-Type:application/x-www-form-urlencoded Body: grant_type:refresh_token refresh_token:refresh_token的值 返回結果示例: { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTYzOTg1MjU2LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYzg4YjM5ZjEtYTE2Yy00OGI3LTg4N2MtMjM3MTc5NDI3MjRhIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.Gmt8xFMqbRcx96rmlhg8AZhrMxDRGorVjK8wV_AEox4", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJhdGkiOiJjODhiMzlmMS1hMTZjLTQ4YjctODg3Yy0yMzcxNzk0MjcyNGEiLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTY2NTc2NTYxLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMjNjYzIyY2EtNmI2OC00ZmJkLTg1OGItZjcxNzVkNzAxOTgyIiwiY2xpZW50X2lkIjoicGFwZXJiZWVDbGllbnQifQ.DY_GvNRS7mJP8FrPorffoqvTokU3udnlYnnFcWv0x4g", "expires_in": 599, "jti": "c88b39f1-a16c-48b7-887c-23717942724a" }
access_token是第三方系統的前端來獲取的,因此對於前端來講,自己就已經保證了token的合法性,用戶的帳號信息能夠直接經過對token的base64解碼來獲取。
可是前端在調用後端接口時也是要攜帶token,爲了防止僞造,第三方的後端服務就須要拿這個token去認證中心的服務器上去校驗token的合法性,並獲取對應token的用戶信息。
臨時提供的接口地址 - {認證中心域名}/uaa/user/parseJwt (GET):
Header: Authorization:bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJrZXJyeSIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJuYW1lIjoi5ZC05pmo55GeIiwiZXhwIjoxNTY0MTk3Mjk1LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg5ZDRlYjgtOTE2ZC00NTM1LTgyMDQtOTEyMjdlNjJhNTgyIiwiY2xpZW50X2lkIjoiYXBpRGVzaWduQ2xpZW50In0.Lgw9LOzK0b2i8w2VrU0NumOKvNBFXoDKIwRNi2UB6vs # bearer access_token的值 返回結果示例: { "user_name": "kerry", "scope": [ "user_info" ], "name": "吳晨瑞", "exp": 1564197295, "authorities": [ "ROLE_USER" ], "jti": "b89d4eb8-916d-4535-8204-91227e62a582", "client_id": "client1" }
受權登陸都是跳轉到認證中心域名下的登陸頁面,在登陸成功後,會在該域名下生成認證的Cookie。
優勢:在同一個瀏覽器上,只要有一個接入單點登陸的第三方系統登陸後,其餘的系統無需登陸就能獲取到受權碼。
缺點:若是想從新登陸,就須要清除當前瀏覽器中,認證中心域下的Cookie。
認證中心已經提供了一個註銷的地址 - {認證中心域名}/uaa/logout ,當的第三方系統頁面上點擊註銷,只須要跳轉到該地址,會自動清除認證中心域名下的Cookie,並重定向回第三方系統的當前頁面。
第三方系統的前端在調用後端接口時,會攜帶 jwt 的 access_token,因爲jwt的密鑰只存在認證中心的服務器上,第三方系統的後臺是沒有辦法去驗證並解析獲取到的access_token的。
因此當客戶端後臺須要獲取當前access_token用戶的信息,只能經過攜帶access_token調用認證中心開放的指定接口,並由認證中心來驗證當前access_token是否合法受權。