前言
為什么整理單點登錄?
主要的原因還是自己以前學習的時候曾經用過,但是時間太久,忘記了里面用到了哪些技術、及如何實現的,每次想到單點登錄總是感覺即會又不會,這次整理session時,又涉及到了單點登錄,而且單點登錄里面還用到了redis,也是我這次要整理的內容,故而單獨把單點登錄抽出來,系統的梳理下,以備下次忘記時重溫。
順便也把以前學習分布式項目中用到的相關技術:double、zookeeper等統一梳理一遍,加強對現在項目的整理架構的理解。
其實,無論是單一的應用,還是分布式項目乃至現在的微服務技術,其實說到底,思想都是相通的,技術也是相通,只有從底層了解始末,融匯貫通,才會對技術的整體有一個把控,不至於迷失在技術進步的潮流中。
一、什么是單點登錄
SSO(Single Sign On)單點登錄是實現多個系統之間統一登錄的驗證系統,簡單來說就是:有A,B,C三個系統,在A處登錄過后,再訪問B系統,B系統就已經處於了登錄狀態,C系統也是一樣。舉個生活中栗子:你同時打開天貓和淘寶,都進入login界面,都要求你登錄的,現在你在淘寶處登錄后,直接在天貓處刷新,你會發現,你已經登錄了,而且就是你在淘寶上登錄的用戶。說明他們實現了SSO,並且持有相同的信息。
二、為什么要用到單點登錄
一次登錄,多處使用。
三、單點登錄的實現
3.1自己什么時候用過單點登錄
培訓時,做的ego項目里搭建過一個單點登錄的子系統。說白了,就是類似於京東、淘寶這樣的一個電商項目。因為采用的是分布式項目結構,各個模塊分別是一個單獨的應用,分屬不同的服務器,想要實現一處登錄,多處使用,肯定不能再用session了。
3.2 實現思路
我們這里的實現思路如下:
Redis+Cookie實現模擬Session功能,實現登錄功能。
- 適應場景 :分布式系統中,才會使用單點登錄!
- 說明:在分布式項目中,一次登錄,項目之間共享登錄狀態。
- 思路:
- 單獨有一個登錄(還包含注冊等)項目;
- 所有其他項目需要登錄,或需要使用用戶登錄后狀態都向登錄項目進行請求。
3.3為什么不能用session與cookie實現
1、跨服務器了,所以不能用session;
2、cookie的話,是存放在瀏覽器端的,不安全;而且用戶也可以禁用cookie(這里指的是存放在本地的cookie,不是會話cookie)。
3、放到注冊中心,zookeeper:不行,如果掛了就不知道用戶是否登錄了。(聯想到了dubbo的健壯性)
4、放到redis里,redis可以搞集群,一個宕機了也沒事。
3.3 代碼
1、關於代碼不再進行copy,相關的具體實現已經看過代碼及視頻了,這里只對實現的思路及過程進行說明。
2、思路
1.用戶登錄 1.1 用戶登錄的時候,向cookie中添加了一個TT_TOKEN值。 1.2 同時,還需要向redis中添加一個user對象 key = user:token value = user對象的json字符串 2.一處登錄多處使用 2.1 每個頁面加載的時候,需要check。當前Cookie[TT_TOKEN]中是否有值 2.2 如果有TT_TOKEN,從redis中將對象取出來! 3.退出的時候: 3.1 刪除cookie 中 TT_TOKEN 3.2 刪除redis中的值!
詳細的代碼參看下面的部分主要代碼。
3、發送請求時相關參數,可以看到有cookie信息。
4、redis存的用戶信息
3.3.1 登錄功能
登錄的功能是在一個單獨的項目ego1-passport中實現的。
具體步驟
1. 需求分析:將登錄的信息存放到redis中,目的是想做sso! 1.1 登錄控制器接收接收兩個參數username和password 1.2 調用Dubbo服務判斷用戶是否登錄成功. 1.3 產生Cookie,存放UUID 1.4 把用戶信息放入到Redis中. 1.5 從哪里跳轉到登錄頁面,再跳轉回去那個頁面. 2. 實現過程,新建一個項目ego-passport,選擇war類型。 3. 導入相關的配置文件。Pom.xml,web.xml,spring相關配置文件。
詳細代碼可以看筆記、視頻去。
1、登錄方法部分代碼
@Value("${redis.user.key}")
private String key; @Override public EgoResult login(TbUser user, HttpServletRequest request, HttpServletResponse response) { EgoResult er = new EgoResult(); // 根據數據庫查詢,查詢出來之后,需要放到Redis中 TbUser user2 = tbUserDubboService.selByUser(user); if (user2!=null) { String uuid = UUID.randomUUID().toString(); // cookieName 是根據前台js來確定。 CookieUtils.setCookie(request, response, "TT_TOKEN", uuid); // 需要存儲到redis 中 key組成是由key+uuid user:uuid user:65bedb06-9931-4fe8-9eb7-7fe1f8846d0b // value:實體類對象登錄對象 jedisPoolDaoImpl.set(key+uuid, JsonUtils.objectToJson(user2)); // 返回值是根據易購商城接口sso登錄來確定 er.setMsg("OK"); er.setStatus(200); } return er; }
可以看出來,在登錄時,實現了如下操作:
- 先查庫,看有沒有這個用戶;
- 用戶存在的話,會在cookie里添加新值,key為token(這里命名為TT_TOKEN),value為uuid。
- 將用戶信息放在redis中,key為user:uuid,value為用戶對象轉譯的json串。
為什么會用到cookie?
如下截圖,其他服務頁面在訪問時,會通過cookie,獲取token,來校驗,如果不存在token的話,會返回,跳轉到登錄界面。
需要理解到的是,這里的cookie是一個會話cookie。
為什么要用token?
校驗用戶是否登錄;
通過token,從redis中獲取用戶信息;
說白了,token就相當於一張通票,一處登陸(相當於買了一張通票,有了它,各個分園都可以去),處處使用(各個服務器都無需再次登錄,並且可以通過token去redis中獲取到用戶信息)。
token是什么?
在下面的延伸里有說明。其實,這里的token值,就是自己編寫的一個隨機uuid,然后放到了cookie中。它在這個分布式項目中就是起到了一個唯一標識並記錄(存在cookie里了)用戶信息的作用。是用戶登錄的憑證。
1、用戶登錄的請求及返回信息簡略
3.3.2 在ego-portal中當用戶登錄成功后,在最上面顯示用戶信息
通過token,從redis中獲取用戶信息。
請求及返回參數示例:
3.3.3 退出功能
刪除redis,同時刪除cookie。
關於sso的介紹與使用,也可以參考下面的鏈接:https://blog.csdn.net/zhangjingao/article/details/81735041
四、延伸
4.1 復習HttpSession和Cookie
4.1.1 HttpSession
2.1 session中文名稱:會話.瀏覽器開啟到關閉的這段時間!
2.2 如何產生的?
2.2.1 在request.getSession();才能產生Session,產生完成后占用系統內存的.
2.3 Session原理.
1、創建Session的時候,服務器將生成一個唯一的sessionid然后用它生成一個關閉瀏覽器就會失效的cookie[JSESSIONID是cookie產生]。 2、然后再將一個與這個sessionid關聯的數據項加入散列表。 例如這樣一段代碼:Session["UserName"]=23; 假設sessionid為123那么散列表中會追加一行 sessionid username 123 23 3、當瀏覽器端提交到服務器時,會通過sessionid=123去散列表中尋找屬於該用戶的Session信息。
4.1.2 cookie
1.1 解釋:客戶端存值技術。
1.1.1 存儲位置:內容存放在客戶端瀏覽器中。
1.1.2 可以存儲什么類型的值:只能存儲文本數據(字符串)。
1.2 Cookie流程
1.2.1 步驟1: 客戶端向服務器端發送請求時,瀏覽器會自動攜帶所有能獲取的Cookie內容.放在請求對象中。
1.2.2 步驟2:所有Cookie產生位置都是服務器端.在服務器端可以創建任意和key-value形式的Cookie對象.並把cookie對象放入到response對象中。
1.2.2.1 在servlet或jsp腳本中Cookie c=new Cookie(“”,””)。
1.2.2.2服務器端通過request.getCookies()獲取cookie。
1.2.3 步驟3:把Cookie響應給客戶端(重定向)。
1.2.4 步驟4:瀏覽器接收到響應后,會從響應對象中獲取到Cookie內容,並把Cookie內容存儲在指定位置。
1.3Cookie幾個概念。
1.3.1 有效時間:
1.3.1.1 默認與Session對象有效時間相同.
1.3.1.2 手動設置Cookie有效時間,固定有效時間.不刷新
//有效時間秒 c.setMaxAge(10);
1.3.2 可訪問路徑問題.
1.3.2.1 設置完成后cookie只能被當前目錄及子目錄進行訪問。
/cookie/jsp/ : cookie表示項目名稱jsp表示在WebContent目錄下的文件夾
c.setPath("/cookie/jsp/");
1.3.3 設置可訪問域名問題。
//防止其他網站獲取cookie內容 c.setDomain("localhost");
案例:
@WebServlet("/TCookie") public class TCookie extends HttpServlet { private static final long serialVersionUID = 1L; // servlet : 中核心方法:service() 方法是處理請求的method="post/get"。 // 如果請求的是get,則會有service()方法,將請求發送給doGet()方法處理,post---doPost(); @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1.創建cookie Cookie cookie = new Cookie("name", "admin"); // 設置cookie 的生命周期 // cookie.setMaxAge(10); // 設置一個訪問路徑 // cookie.setPath("/testcookie/jsp/"); // 設置一個域名方法 cookie.setDomain("localhost"); // 2.將cookie添加到response中 response.addCookie(cookie); // 3.將cookie發送到客戶端 response.sendRedirect("index.jsp"); } }
前端:
/testcookie/jsp/index1.jsp <body> <h1>index1 ------------</h1> <!-- 取cookie --> <!-- 使用小腳本來取得cookie對象 --> <% /* 數組定義: 數據類型 [] 數組名稱 = new 數據類型[長度] 基本數據類型,引用數據類型 */ Cookie [] c = request.getCookies(); /* 增強for循環 for(數據類型 變量名 : 數組/集合){} */ for(Cookie cookie:c){ /* jsp九大內置對象。誰是輸出out */ out.println(cookie.getName()+"======"+cookie.getValue()+"<br/>"); } %> </body>
4.2 token
token是計算機術語:令牌,令牌是一種能夠控制站點占有媒體的特殊幀,以區別數據幀及其他控制幀。token其實說的更通俗點可以叫暗號,在一些數據傳輸之前,要先進行暗號的核對,不同的暗號被授權不同的數據操作。基於 Token 的身份驗證方法
使用基於 Token 的身份驗證方法,在服務端不需要存儲用戶的登錄記錄。大概的流程是這樣的:
1.客戶端使用用戶名跟密碼請求登錄
2.服務端收到請求,去驗證用戶名與密碼
3.驗證成功后,服務端會簽發一個 Token,再把這個 Token 發送給客戶端
4.客戶端收到 Token 以后可以把它存儲起來,比如放在 Cookie 里或者 Local Storage 里
5.客戶端每次向服務端請求資源的時候需要帶着服務端簽發的 Token
6.服務端收到請求,然后去驗證客戶端請求里面帶着的 Token,如果驗證成功,就向客戶端返回請求的數據