只有光頭才能變強。html
文本已收錄至個人GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3yjava
在我實習以前我就已經在看單點登陸的是什麼了,可是實習的時候一直在忙其餘的事,因此有幾個網站就一直躺在個人收藏夾裏邊:git
在前陣子有個讀者來我這投稿,是使用JWT實現單點登陸的(可是文章中並無介紹什麼是單點登陸),因此我以爲是時候來整理一下了。github
單點登陸的英文名叫作:Single Sign On(簡稱SSO)。web
在初學/之前的時候,通常咱們就單系統,全部的功能都在同一個系統上。redis
後來,咱們爲了合理利用資源和下降耦合性,因而把單系統拆分成多個子系統。數據庫
好比阿里系的淘寶和天貓,很明顯地咱們能夠知道這是兩個系統,可是你在使用的時候,登陸了天貓,淘寶也會自動登陸。json
簡單來講,單點登陸就是在多個系統中,用戶只需一次登陸,各個系統便可感知該用戶已經登陸。跨域
在我初學JavaWeb的時候,登陸和註冊是我作得最多的一個功能了(初學Servlet的時候作過、學SpringMVC的時候作過、跟着作項目的時候作過…),反正我也數不清我作了多少次登陸和註冊的功能了...這裏簡單講述一下咱們初學時是怎麼作登陸功能的。瀏覽器
衆所周知,HTTP是無狀態的協議,這意味着服務器沒法確認用戶的信息。因而乎,W3C就提出了:給每個用戶都發一個通行證,不管誰訪問的時候都須要攜帶通行證,這樣服務器就能夠從通行證上確認用戶的信息。通行證就是Cookie。
若是說Cookie是檢查用戶身上的」通行證「來確認用戶的身份,那麼Session就是經過檢查服務器上的」客戶明細表「來確認用戶的身份的。Session至關於在服務器中創建了一份「客戶明細表」。
HTTP協議是無狀態的,Session不能依據HTTP鏈接來判斷是否爲同一個用戶。因而乎:服務器向用戶瀏覽器發送了一個名爲JESSIONID的Cookie,它的值是Session的id值。其實Session是依據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保存在數據庫中*/ 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 * <p> * 攔截器;實現自動登錄功能 */ 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"); // 已經登錄了,放行 if (sessionUser != null) { return true; } else { //獲得帶過來cookie是否存在 String loginToken = CookieUtil.findCookieByName(request, "loginToken"); if (StringUtils.isNotBlank(loginToken)) { //到數據庫查詢有沒有該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; } } } }
總結一下上面代碼的思路:
若是沒看懂的同窗,建議回顧Session和Cookie和HTTP:
單系統登陸功能主要是用Session保存用戶信息來實現的,但咱們清楚的是:多系統便可能有多個Tomcat,而Session是依賴當前系統的Tomcat,因此係統A的Session和系統B的Session是不共享的。
解決系統之間Session不共享問題有一下幾種方案:
咱們能夠將登陸功能單獨抽取出來,作成一個子系統。
SSO(登陸系統)的邏輯以下:
// 登陸功能(SSO單獨的服務) @Override public TaotaoResult login(String username, String password) throws Exception { //根據用戶名查詢用戶信息 TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); List<TbUser> 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())) { 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); }
其餘子系統登陸時,請求SSO(登陸系統)進行登陸,將返回的token寫到Cookie中,下次訪問時則把Cookie帶上:
public TaotaoResult login(String username, String password, HttpServletRequest request, HttpServletResponse response) { //請求參數 Map<String, String> 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; }
總結:
到這裏,其實咱們會發現其實就兩個變化:
上面咱們解決了Session不能共享的問題,但其實還有另外一個問題。Cookie是不能跨域的
好比說,咱們請求<https://www.google.com/>
時,瀏覽器會自動把google.com
的Cookie帶過去給google
的服務器,而不會把<https://www.baidu.com/>
的Cookie帶過去給google
的服務器。
這就意味着,因爲域名不一樣,用戶向系統A登陸後,系統A返回給瀏覽器的Cookie,用戶再請求系統B的時候不會將系統A的Cookie帶過去。
針對Cookie存在跨域問題,有幾種解決方案:
到這裏,咱們已經能夠實現單點登陸了。
說到單點登陸,就確定會見到這個名詞:CAS (Central Authentication Service),下面說說CAS是怎麼搞的。
若是已經將登陸單獨抽取成系統出來,咱們還能這樣玩。如今咱們有兩個系統,分別是www.java3y.com
和www.java4y.com
,一個SSOwww.sso.com
首先,用戶想要訪問系統Awww.java3y.com
受限的資源(好比說購物車功能,購物車功能須要登陸後才能訪問),系統Awww.java3y.com
發現用戶並無登陸,因而重定向到sso認證中心,並將本身的地址做爲參數。請求的地址以下:
www.sso.com?service=www.java3y.com
sso認證中心發現用戶未登陸,將用戶引導至登陸頁面,用戶進行輸入用戶名和密碼進行登陸,用戶與認證中心創建全局會話(生成一份Token,寫到Cookie中,保存在瀏覽器上)
隨後,認證中心重定向回系統A,並把Token攜帶過去給系統A,重定向的地址以下:
www.java3y.com?token=xxxxxxx
接着,系統A去sso認證中心驗證這個Token是否正確,若是正確,則系統A和用戶創建局部會話(建立Session)。到此,系統A和用戶已是登陸狀態了。
此時,用戶想要訪問系統Bwww.java4y.com
受限的資源(好比說訂單功能,訂單功能須要登陸後才能訪問),系統Bwww.java4y.com
發現用戶並無登陸,因而重定向到sso認證中心,並將本身的地址做爲參數。請求的地址以下:
www.sso.com?service=www.java4y.com
注意,由於以前用戶與認證中心www.sso.com
已經創建了全局會話(當時已經把Cookie保存到瀏覽器上了),因此此次系統B重定向到認證中心www.sso.com
是能夠帶上Cookie的。
認證中心根據帶過來的Cookie發現已經與用戶創建了全局會話了,認證中心重定向回系統B,並把Token攜帶過去給系統B,重定向的地址以下:
www.java4y.com?token=xxxxxxx
接着,系統B去sso認證中心驗證這個Token是否正確,若是正確,則系統B和用戶創建局部會話(建立Session)。到此,系統B和用戶已是登陸狀態了。
看到這裏,其實SSO認證中心就相似一個中轉站。
參考資料:
樂於輸出乾貨的Java技術公衆號:Java3y。公衆號內有200多篇原創技術文章、海量視頻資源、精美腦圖,關注便可獲取!
以爲個人文章寫得不錯,點贊!