JWT打造無限擴充套件登入中心
前言:
現在人的賬號越來越多,大家對新註冊賬號越來越反感。這時候,當一個公司有多個產品時,打通各個應用壁壘,使用一個統一的登入中心。實現一號全網通,或是隻需登入一次的單點登入。都是非常有必要的。今天介紹的就是如何打造一款可以無限接入的登入中心。在這個登入中心中,我選擇使用了JWT來做加密。來更大擴充套件開發邊界。讓多種語言也可以輕鬆完成接入
登入時序圖

登入時序圖
這是一個使用者沒有登入的情況的圖
資料表交代
-
app表
app表
app表主要用來儲存業務方應用的回撥地址,appkey,appid這個三個重要的引數,appid就是主鍵id
-
user表
user表
使用者表就比較隨意了。大家可以根據自己的需要來設計
登入中心部分
首先介紹下登入中心這個產品:
Q:登入中心是做什麼的?
A:登入中心是用來做登入的,每個應用都到這裡來登入,獲取授權。登入成功後會回到訪問的應用中
Q:登入中心是如何實現無線擴充套件且支援多語言的?
A:依靠之前展示過的app表,任何的接入方都需要先註冊這個app表。多語言支援是因為使用了JWT來做加解密。這個技術幾乎支援市面上所有的語言
好了 Talk is cheap.Show me your code!
-
當用戶去瀏覽第三方的應用的時候,第三方會去檢查他的登入情況,如果沒有登入,就會重定向到登入中心。並且帶上自己的appid和當前使用者所訪問的頁面url
引數名:appid,callback_url
這兩個引數實質上是用於校驗請求是否合法
有了appid 我們就可以去app表裡獲得當初註冊的回撥地址 。我們將註冊時回撥地址和傳遞過來的回撥地址拿來對比看域名是否一致。
如果appid不存在或回撥地址不一致,則視為非法
- 如果校驗通過,則需要判斷登入狀態的密文是否在cookie中存在。
假如一個使用者在瀏覽應用A時做過登入。在登入中心會儲存一份他登入的密文。當用戶瀏覽應用B時,他也會被重定向到登入中心,這時,發現他已經登入,則會直接帶上密文重定向到應用B。無需再登入,也就是單點登入
- 如果在不存在cookie。則渲染登入頁面。引導登入
登入後驗證賬號密碼。賬號密碼出錯自然就結束了。驗證通過後,會製作一條包含登入資訊的密文,這裡使用JWT的依賴包。隨後寫入登入中心的cookie。在重定向到應用方的註冊的回撥地址,並帶上兩個引數
1.code //這個就是我們生成的密文
2.redirect //這個就是使用者最初訪問的那個地址
/** * @throws \think\exception\DbException */ public function sso() { /** * 1.檢查引數是否存在(appid,callback_url) * 2.根據appid查詢app表,callback_url 是否正確 * 3.判斷是否有cookie * 4.如果有cookie * 5.重定向到callback_url?code=cookie裡的密文 * 6.如果沒有cookie,渲染登入頁 */ $requestObj = request(); $appId = $requestObj->get('appid'); $callbackUrl = $requestObj->get('callback_url'); if (empty($appId) || empty($callbackUrl)) { $this->error('引數不能為空'); } //校驗引數格式 $urlArr = parse_url($callbackUrl); if ($urlArr == false || !is_numeric($appId) || !array_key_exists('host', $urlArr)) { $this->error('引數錯誤'); } //查詢應用 $data = App::get($appId); if (empty($data)) { $this->error('應用不存在'); } //取出域名 $Uhost = $urlArr['host']; $Dhost = parse_url($data['callback_url'])['host']; //對比域名 if ($Uhost != $Dhost) { $this->error('回撥地址不正確'); } $authCode = cookie('authCode'); if (empty($authCode)) { //將回調地址賦值給模板 $this->assign('callback_url',$callbackUrl); //app表中的回撥地址 $this->assign('redirect',$data['callback_url']); //引導登入,渲染登入頁 return $this->fetch(); } else { //拼接回調地址 $url=BaseService::assembleUrl($data['callback_url'],$callbackUrl,'code',$authCode); $this->redirect($url); } }
在登入中心下有cookie的情況下。會呼叫個自定義的方法assembleUrl()
這個使用來拼接url的
/** * 製作用於重定向 業務方的地址 * @param $url * @param $callBackurl * @param $key * @param $value * @return string */ public static function assembleUrl($url,$callBackurl,$key,$value) { //有登入記錄 if (strpos($url, "?")) { //包含? $url = $url . "&$key=" . $value; } else { $url = $url . "?$key=" . $value; } //最終拼接上使用者訪問的url $url.="&redirect={$callBackurl}"; return $url; }
那麼沒有cookie的情況下就要引導登入對不對。上面我也提到了,渲染登入頁面。這裡前端程式碼我就不貼了。
大概長這個樣子

登入中心頁面
這裡有兩個小細節,就是我在渲染模板之前,我給模板賦值。將app表裡存的回撥地址和使用者最初訪問的url地址都賦值給了模板。目的是為了提交表單的時候把地址一併帶過來。方便我們做重定向。
那麼使用者輸入賬號密碼之後,就會提交到登入方法中。登入方法的思路其實就比較簡單了
- 驗證賬號
- 製作authCode
- 製作重定向地址
- 重定向
/** /** * @param $username * 賬號 * @param $password * 密碼 * @param $callback_url * 使用者訪問的url * @param $redirect * 業務方註冊的回撥地址 */ public function login($username,$password,$redirect,$callback_url) { if (empty($username)||empty($password)) { $this->error('賬號或密碼不能為空'); } try{ $data['username']=$username; $data['password']=$password; $authService=new Auth(); $token= $authService->makeAuthCode($data,$redirect); //將token寫入cookie cookie('authCode',$token,3600); //拼裝 $url=BaseService::assembleUrl($redirect,$callback_url,'code',$token); $this->redirect($url); }catch (Exception $e){ $this->error($e->getMessage()); } }
下面我們重點來關注下authservice中的makeAuthCode方法
/** *利用jwt製作密文 * @param $data * @param $redirect * @return \Lcobucci\JWT\Token * @throws Exception * @throws \think\exception\DbException */ public function makeAuthCode($data,$redirect) { $user = Base::checkUser($data); if (empty($user)) { throw new Exception('賬號或密碼錯誤'); } //獲取該應用祕鑰 $app=App::get(['callback_url'=>$redirect]); if (empty($app)) { throw new Exception('應用非法'); } //拿到該應用的祕鑰 $key=$app['app_key']; //製作jwt $jwtBuilder = new Builder(); //設定加密物件 $signer = new Sha256(); //設定簽發者 $jwtBuilder->setIssuer('[email protected]'); //設定簽發時間 $jwtBuilder->setIssuedAt(time()); //設定當前時間不能早於設定時間 $jwtBuilder->setNotBefore(time() + 10); //設定過期時間 $jwtBuilder->setExpiration(time() + 3600); //設定uid $jwtBuilder->set('uid', $user['id']); //設定username $jwtBuilder->set('name', $user['name']); //使用演算法簽名 $jwtBuilder->sign($signer, $key); //呼叫獲取token方法 $token = $jwtBuilder->getToken(); return $token; }
這裡我使用的是jwt3.0 php的包。裡面有對生成token的封裝。實際上jwt的加密方式是公開的,你要是理解透徹,自己來做加密也是可以的。網上有很多教程。我就簡單解釋下jwt的構成
jwt的結構很簡單,分為三層
- header層
header層就和http的header作用類似,主要是用於宣告,宣告自己的json型別,宣告自己的加密方法 - payload層
payload裡面會包含claim 也就是實體。我的理解就是一些我們需要傳遞的資料,當然官方有推薦使用的一些欄位,比如iss(簽發者),exp(過期時間)等等。在加上你需要傳遞的欄位和資料就構成了payload層 - signature層
簽名層就是將header和payload 按照header中約定的加密方法加上一個祕鑰進行簽名。解密方,也同時擁有這個祕鑰可以使用同樣的方式進行驗籤
在把這個生成好了的token,儲存一份在登入中心的cookie。並且使用之前介紹過的拼接跳轉url方法,將token賦值給code做重定向。這樣業務方域名下就會在最初註冊的回撥地址下收到兩個get形式的引數
- code(也就是包含使用者id,name的jwt)
- redirect (使用者最初訪問的連結) //一波重定向後還是要讓使用者回到最初訪問的地方
業務方只需對code解密驗籤,就可以拿到使用者的登入資訊。將這個code存入cookie。在每次請求的時候都帶上這個cookie。就可以拿來做登入驗證了。
寫好cookie後重定向到使用者最初訪問的地址就完成了本次登入
好了。關於登入中心的介紹我就先寫到這裡。其實還有挺多需要完善的地方。比如退出登入。如果要徹底退出,則需要清除登入中心域名下的cookie。比如業務方僅僅需要賬號接入,並不希望單點登入的情況。登入中心下就不儲存cookie版本。看看有沒有時間去寫吧。現在寫的這是一個demo版本。如果有時間,寫完善後我會傳到github上面去。
之後我會寫一篇,關於接入方。接入登入中心的部落格。來詳細介紹下。關於接入的細節。謝謝