我們經(jīng)常提到的單點登錄(SSO)到底是什么東西?

      網(wǎng)友投稿 869 2022-05-29

      今天因為拍畢業(yè)照以及畢業(yè)設計一些事情,所以這么晚才更新公眾號。因為大三的分班導致現(xiàn)在我連班里很多人都不認識,拍畢業(yè)照也感覺沒有了高三那時候的感覺。我現(xiàn)在的狀態(tài)應該是屬于開心與憂慮并存吧!畢竟畢業(yè)了就要進入社會,可能就再也不能像在學校這樣隨心所欲,這樣悠閑了。校園的生活真的很美好,不過我們總要面對進入社會這個現(xiàn)實,為了養(yǎng)家,或者說的好聽點是為了實現(xiàn)自我價值。

      分享一篇朋友Java3y寫的很不錯的單點登錄的文章給各位朋友,他的公眾號的名字也是Java3y!共勉!

      前言

      在我實習之前我就已經(jīng)在看單點登錄的是什么了,但是實習的時候一直在忙其他的事,所以有幾個網(wǎng)站就一直躺在我的夾里邊:

      的一些網(wǎng)站

      在前陣子有個讀者來我這投稿,是使用JWT實現(xiàn)單點登錄的(但是文章中并沒有介紹什么是單點登錄),所以我覺得是時候來整理一下了。

      一、什么是單點登錄?

      單點登錄的英文名叫做:Single Sign On(簡稱SSO)。

      在初學/以前的時候,一般我們就單系統(tǒng),所有的功能都在同一個系統(tǒng)上。

      所有的功能都在同一個系統(tǒng)上

      后來,我們?yōu)榱撕侠砝觅Y源和降低耦合性,于是把單系統(tǒng)拆分成多個子系統(tǒng)。

      回顧:分布式基礎知識

      拆分成多個子系統(tǒng)

      拆分成多個子系統(tǒng)

      比如阿里系的淘寶和天貓,很明顯地我們可以知道這是兩個系統(tǒng),但是你在使用的時候,登錄了天貓,淘寶也會自動登錄。

      登錄了天貓,淘寶也登錄了

      簡單來說,單點登錄就是在多個系統(tǒng)中,用戶只需一次登錄,各個系統(tǒng)即可感知該用戶已經(jīng)登錄。

      二、回顧單系統(tǒng)登錄

      在我初學JavaWeb的時候,登錄和注冊是我做得最多的一個功能了(初學Servlet的時候做過、學SpringMVC的時候做過、跟著做項目的時候做過…),反正我也數(shù)不清我做了多少次登錄和注冊的功能了…這里簡單講述一下我們初學時是怎么做登錄功能的。

      HTTP是無狀態(tài)的協(xié)議

      眾所周知,HTTP是無狀態(tài)的協(xié)議,這意味著服務器無法確認用戶的信息。于是乎,W3C就提出了:給每一個用戶都發(fā)一個通行證,無論誰訪問的時候都需要攜帶通行證,這樣服務器就可以從通行證上確認用戶的信息。通行證就是Cookie。

      如果說Cookie是檢查用戶身上的”通行證“來確認用戶的身份,那么Session就是通過檢查服務器上的”客戶明細表“來確認用戶的身份的。Session相當于在服務器中建立了一份“客戶明細表”。

      HTTP協(xié)議是無狀態(tài)的,Session不能依據(jù)HTTP連接來判斷是否為同一個用戶。于是乎:服務器向用戶瀏覽器發(fā)送了一個名為JESSIONID的Cookie,它的值是Session的id值。其實Session是依據(jù)Cookie來識別是否是同一個用戶。

      所以,一般我們單系統(tǒng)實現(xiàn)登錄會這樣做:

      登錄:將用戶信息保存在Session對象中

      - 如果在Session對象中能查到,說明已經(jīng)登錄

      如果在Session對象中查不到,說明沒登錄(或者已經(jīng)退出了登錄)

      注銷(退出登錄):從Session中刪除用戶的信息

      記住我(關閉掉瀏覽器后,重新打開瀏覽器還能保持登錄狀態(tài)):配合Cookie來用

      我之前Demo的代碼,可以參考一下:

      /**

      *?用戶登陸

      */

      @PostMapping(value?=?"/user/session",?produces?=?{"application/json;charset=UTF-8"})

      public?Result?login(String?mobileNo,?String?password,?String?inputCaptcha,?HttpSession?session,?HttpServletResponse?response)?{

      //判斷驗證碼是否正確

      if?(WebUtils.validateCaptcha(inputCaptcha,?"captcha",?session))?{

      //判斷有沒有該用戶

      User?user?=?userService.userLogin(mobileNo,?password);

      if?(user?!=?null)?{

      /*設置自動登陸,一個星期.??將token保存在數(shù)據(jù)庫中*/

      String?loginToken?=?WebUtils.md5(new?Date().toString()?+?session.getId());

      user.setLoginToken(loginToken);

      User?user1?=?userService.userUpload(user);

      session.setAttribute("user",?user1);

      CookieUtil.addCookie(response,"loginToken",loginToken,604800);

      return?ResultUtil.success(user1);

      }?else?{

      return?ResultUtil.error(ResultEnum.LOGIN_ERROR);

      }

      }?else?{

      return?ResultUtil.error(ResultEnum.CAPTCHA_ERROR);

      }

      }

      /**

      *?用戶退出

      */

      @DeleteMapping(value?=?"/session",?produces?=?{"application/json;charset=UTF-8"})

      public?Result?logout(HttpSession?session,HttpServletRequest?request,HttpServletResponse?response?)?{

      //刪除session和cookie

      session.removeAttribute("user");

      CookieUtil.clearCookie(request,?response,?"loginToken");

      return?ResultUtil.success();

      }

      /**

      *?@author?ozc

      *?@version?1.0

      *?

      *?-;實現(xiàn)自動登陸功能

      */

      public?class?UserInterceptor?implements?HandlerInterceptor?{

      @Autowired

      private?UserService?userService;

      public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?o)?throws?Exception?{

      User?sessionUser?=?(User)?request.getSession().getAttribute("user");

      //?已經(jīng)登陸了,放行

      if?(sessionUser?!=?null)?{

      return?true;

      }?else?{

      //得到帶過來cookie是否存在

      String?loginToken?=?CookieUtil.findCookieByName(request,?"loginToken");

      if?(StringUtils.isNotBlank(loginToken))?{

      //到數(shù)據(jù)庫查詢有沒有該Cookie

      User?user?=?userService.findUserByLoginToken(loginToken);

      if?(user?!=?null)?{

      request.getSession().setAttribute("user",?user);

      return?true;

      }?else?{

      //沒有該Cookie與之對應的用戶(Cookie不匹配)

      CookieUtil.clearCookie(request,?response,?"loginToken");

      return?false;

      }

      }?else?{

      //沒有cookie、也沒有登陸。是index請求獲取用戶信息,可以放行

      if?(request.getRequestURI().contains("session"))?{

      return?true;

      }

      //沒有cookie憑證

      response.sendRedirect("/login.html");

      return?false;

      }

      }

      }

      }

      總結(jié)一下上面代碼的思路:

      用戶登錄時,驗證用戶的賬戶和密碼

      生成一個Token保存在數(shù)據(jù)庫中,將Token寫到Cookie中

      將用戶數(shù)據(jù)保存在Session中

      請求時都會帶上Cookie,檢查有沒有登錄,如果已經(jīng)登錄則放行

      Cookie的作用是什么?和Session有什么區(qū)別?

      Cookie 和 Session都是用來跟蹤瀏覽器用戶身份的會話方式,但是兩者的應用場景不太一樣。

      Cookie 一般用來保存用戶信息?比如①我們在 Cookie 中保存已經(jīng)登錄過得用戶信息,下次訪問網(wǎng)站的時候頁面可以自動幫你登錄的一些基本信息給填了;②一般的網(wǎng)站都會有保持登錄也就是說下次你再訪問網(wǎng)站的時候就不需要重新登錄了,這是因為用戶登錄的時候我們可以存放了一個 Token 在 Cookie 中,下次登錄的時候只需要根據(jù) Token 值來查找用戶即可(為了安全考慮,重新登錄一般要將 Token 重寫);③登錄一次網(wǎng)站后訪問網(wǎng)站其他頁面不需要重新登錄。Session 的主要作用就是通過服務端記錄用戶的狀態(tài)。典型的場景是購物車,當你要添加商品到購物車的時候,系統(tǒng)不知道是哪個用戶操作的,因為 HTTP 協(xié)議是無狀態(tài)的。服務端給特定的用戶創(chuàng)建特定的 Session 之后就可以標識這個用戶并且跟蹤這個用戶了。

      Cookie 數(shù)據(jù)保存在客戶端(瀏覽器端),Session 數(shù)據(jù)保存在服務器端。

      Cookie 存儲在客戶端中,而Session存儲在服務器上,相對來說 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要寫入 Cookie 中,最好能將 Cookie 信息加密然后使用到的時候再去服務器端解密。

      三、多系統(tǒng)登錄的問題與解決

      3.1 Session不共享問題

      單系統(tǒng)登錄功能主要是用Session保存用戶信息來實現(xiàn)的,但我們清楚的是:多系統(tǒng)即可能有多個Tomcat,而Session是依賴當前系統(tǒng)的Tomcat,所以系統(tǒng)A的Session和系統(tǒng)B的Session是不共享的。

      系統(tǒng)A的Session和系統(tǒng)B的Session是不共享的

      解決系統(tǒng)之間Session不共享問題有一下幾種方案:

      Tomcat集群Session全局復制(集群內(nèi)每個tomcat的session完全同步)【會影響集群的性能呢,不建議】

      根據(jù)請求的IP進行Hash映射到對應的機器上(這就相當于請求的IP一直會訪問同一個服務器)【如果服務器宕機了,會丟失了一大部分Session的數(shù)據(jù),不建議】

      把Session數(shù)據(jù)放在Redis中(使用Redis模擬Session)【建議】

      我們可以將登錄功能單獨抽取出來,做成一個子系統(tǒng)。

      抽取出來成為子系統(tǒng)

      抽取出來成為子系統(tǒng)

      SSO(登錄系統(tǒng))的邏輯如下:

      //?登錄功能(SSO單獨的服務)

      @Override

      public?TaotaoResult?login(String?username,?String?password)?throws?Exception?{

      //根據(jù)用戶名查詢用戶信息

      TbUserExample?example?=?new?TbUserExample();

      Criteria?criteria?=?example.createCriteria();

      criteria.andUsernameEqualTo(username);

      List?list?=?userMapper.selectByExample(example);

      if?(null?==?list?||?list.isEmpty())?{

      return?TaotaoResult.build(400,?"用戶不存在");

      }

      //核對密碼

      TbUser?user?=?list.get(0);

      if?(!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword()))?{

      我們經(jīng)常提到的單點登錄(SSO)到底是什么東西?

      return?TaotaoResult.build(400,?"密碼錯誤");

      }

      //登錄成功,把用戶信息寫入redis

      //生成一個用戶token

      String?token?=?UUID.randomUUID().toString();

      jedisCluster.set(USER_TOKEN_KEY?+?":"?+?token,?JsonUtils.objectToJson(user));

      //設置session過期時間

      jedisCluster.expire(USER_TOKEN_KEY?+?":"?+?token,?SESSION_EXPIRE_TIME);

      return?TaotaoResult.ok(token);

      }

      其他子系統(tǒng)登錄時,請求SSO(登錄系統(tǒng))進行登錄,將返回的token寫到Cookie中,下次訪問時則把Cookie帶上:

      public?TaotaoResult?login(String?username,?String?password,

      HttpServletRequest?request,?HttpServletResponse?response)?{

      //請求參數(shù)

      Map?param?=?new?HashMap<>();

      param.put("username",?username);

      param.put("password",?password);

      //登錄處理

      String?stringResult?=?HttpClientUtil.doPost(REGISTER_USER_URL?+?USER_LOGIN_URL,?param);

      TaotaoResult?result?=?TaotaoResult.format(stringResult);

      //登錄出錯

      if?(result.getStatus()?!=?200)?{

      return?result;

      }

      //登錄成功后把取token信息,并寫入cookie

      String?token?=?(String)?result.getData();

      //寫入cookie

      CookieUtils.setCookie(request,?response,?"TT_TOKEN",?token);

      //返回成功

      return?result;

      }

      總結(jié):

      SSO系統(tǒng)生成一個token,并將用戶信息存到Redis中,并設置過期時間

      其他系統(tǒng)請求SSO系統(tǒng)進行登錄,得到SSO返回的token,寫到Cookie中

      每次請求時,Cookie都會帶上,-得到token,判斷是否已經(jīng)登錄

      到這里,其實我們會發(fā)現(xiàn)其實就兩個變化:

      將登陸功能抽取為一個系統(tǒng)(SSO),其他系統(tǒng)請求SSO進行登錄

      本來將用戶信息存到Session,現(xiàn)在將用戶信息存到Redis

      3.2 Cookie跨域的問題

      上面我們解決了Session不能共享的問題,但其實還有另一個問題。Cookie是不能跨域的

      比如說,我們請求時,瀏覽器會自動把google.com的Cookie帶過去給google的服務器,而不會把的Cookie帶過去給google的服務器。

      這就意味著,由于域名不同,用戶向系統(tǒng)A登錄后,系統(tǒng)A返回給瀏覽器的Cookie,用戶再請求系統(tǒng)B的時候不會將系統(tǒng)A的Cookie帶過去。

      針對Cookie存在跨域問題,有幾種解決方案:

      服務端將Cookie寫到客戶端后,客戶端對Cookie進行解析,將Token解析出來,此后請求都把這個Token帶上就行了

      多個域名共享Cookie,在寫到客戶端的時候設置Cookie的domain。

      將Token保存在SessionStroage中(不依賴Cookie就沒有跨域的問題了)

      到這里,我們已經(jīng)可以實現(xiàn)單點登錄了。

      3.3 CAS原理

      說到單點登錄,就肯定會見到這個名詞:CAS (Central Authentication Service),下面說說CAS是怎么搞的。

      如果已經(jīng)將登錄單獨抽取成系統(tǒng)出來,我們還能這樣玩。現(xiàn)在我們有兩個系統(tǒng),分別是www.java3y.com和www.jav***.com,一個SSOwww.sso.com

      現(xiàn)在我們有三個系統(tǒng)

      首先,用戶想要訪問系統(tǒng)Awww.java3y.com受限的資源(比如說購物車功能,購物車功能需要登錄后才能訪問),系統(tǒng)Awww.java3y.com發(fā)現(xiàn)用戶并沒有登錄,于是重定向到sso認證中心,并將自己的地址作為參數(shù)。請求的地址如下:

      www.sso.com?service=www.java3y.com

      sso認證中心發(fā)現(xiàn)用戶未登錄,將用戶引導至登錄頁面,用戶進行輸入用戶名和密碼進行登錄,用戶與認證中心建立全局會話(生成一份Token,寫到Cookie中,保存在瀏覽器上)

      4步過程

      隨后,認證中心重定向回系統(tǒng)A,并把Token攜帶過去給系統(tǒng)A,重定向的地址如下:

      www.java3y.com?token=xxxxxxx

      接著,系統(tǒng)A去sso認證中心驗證這個Token是否正確,如果正確,則系統(tǒng)A和用戶建立局部會話(創(chuàng)建Session)。到此,系統(tǒng)A和用戶已經(jīng)是登錄狀態(tài)了。

      第五步和第六步

      此時,用戶想要訪問系統(tǒng)Bwww.jav***.com受限的資源(比如說訂單功能,訂單功能需要登錄后才能訪問),系統(tǒng)Bwww.jav***.com發(fā)現(xiàn)用戶并沒有登錄,于是重定向到sso認證中心,并將自己的地址作為參數(shù)。請求的地址如下:

      www.sso.com?service=www.jav***.com

      注意,因為之前用戶與認證中心www.sso.com已經(jīng)建立了全局會話(當時已經(jīng)把Cookie保存到瀏覽器上了),所以這次系統(tǒng)B重定向到認證中心www.sso.com是可以帶上Cookie的。

      認證中心根據(jù)帶過來的Cookie發(fā)現(xiàn)已經(jīng)與用戶建立了全局會話了,認證中心重定向回系統(tǒng)B,并把Token攜帶過去給系統(tǒng)B,重定向的地址如下:

      www.jav***.com?token=xxxxxxx

      接著,系統(tǒng)B去sso認證中心驗證這個Token是否正確,如果正確,則系統(tǒng)B和用戶建立局部會話(創(chuàng)建Session)。到此,系統(tǒng)B和用戶已經(jīng)是登錄狀態(tài)了。

      系統(tǒng)B的流程圖

      看到這里,其實SSO認證中心就類似一個中轉(zhuǎn)站。

      參考

      https://www.cnblogs.com/EzrealLiu/p/5559255.html

      http://www.cnblogs.com/ywlaker/p/6113927.html

      https://blog.csdn.net/javaloveiphone/article/details/5243961

      網(wǎng)站 登錄 數(shù)據(jù)庫

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:華為ServiceTurbo Cloud,聚合行業(yè)應用,為新基建添磚加瓦
      下一篇:Web前端面試真題CSS系列---帶詳解!
      相關文章
      亚洲第一页日韩专区| 国产精品亚洲精品久久精品 | 国产精品亚洲视频| 另类小说亚洲色图| 亚洲Aⅴ在线无码播放毛片一线天 亚洲avav天堂av在线网毛片 | 亚洲AV人无码综合在线观看 | 亚洲色成人四虎在线观看| 激情内射亚洲一区二区三区爱妻| 久久久婷婷五月亚洲97号色| 91在线精品亚洲一区二区| 久久av无码专区亚洲av桃花岛| 久久亚洲日韩精品一区二区三区| 亚洲大片在线观看| 久久夜色精品国产噜噜噜亚洲AV| 亚洲资源在线观看| 亚洲精品资源在线| 亚洲videos| 亚洲国产成人无码AV在线影院| 亚洲AV成人片无码网站| 亚洲成a人片在线观看老师| 亚洲欧洲一区二区三区| 亚洲综合图色40p| 亚洲AV无码欧洲AV无码网站| 久久亚洲精品成人av无码网站| 亚洲黄色免费网址| 亚洲国产日韩在线| 亚洲综合在线一区二区三区| 亚洲高清国产拍精品熟女| 国产91成人精品亚洲精品| 色噜噜亚洲精品中文字幕| 亚洲av无码一区二区三区网站| 久久久无码精品亚洲日韩蜜臀浪潮| 亚洲熟妇无码久久精品| 国产亚洲国产bv网站在线| 亚洲AV成人片无码网站| 久久乐国产精品亚洲综合| 亚洲av无码一区二区乱子伦as| 亚洲精品国产情侣av在线| 香蕉大伊亚洲人在线观看| 日韩亚洲国产综合久久久| 丝袜熟女国偷自产中文字幕亚洲|