【No.1】基於Cookie的單點登錄(SSO)

這篇主要說明基於Cookie的單點登錄實現,以及Cookie的一些特性以及使用說明。

1、Cookie是什麼,如何工作的

      在程序中,會話跟蹤是很重要的事情。理論上,一個用戶的所有請求操作都應該屬於同一個會話,而另一個用戶的所有請求操作則應該屬於另一個會話,二者不能混淆。例如,用戶A在超市購買的任何商品都應該放在A的購物車內,不論是用戶A什麼時間購買的,這都是屬於同一個會話的,不能放入用戶B或用戶C的購物車內,這不屬於同一個會話。而Web應用程序是使用HTTP協議傳輸數據的。HTTP協議是無狀態的協議。一旦數據交換完畢,客戶端與服務器端的連接就會關閉,再次交換數據需要建立新的連接。這就意味着服務器無法從連接上跟蹤會話。即用戶A購買了一件商品放入購物車內,當再次購買商品時服務器已經無法判斷該購買行爲是屬於用戶A的會話還是用戶B的會話了。要跟蹤該會話,必須引入一種機制。
      Cookie就是這樣的一種機制。它可以彌補HTTP協議無狀態的不足。在Session出現之前,基本上所有的網站都採用Cookie來跟蹤會話。
      Cookie意爲“甜餅”,是由W3C組織提出,最早由Netscape社區發展的一種機制。目前Cookie已經成爲標準,所有的主流瀏覽器如IE、Netscape、Firefox、Opera等都支持Cookie。

2、現實生活中類似於Cookie的舉例

      例如你所在的城市可能有很多便利店(web服務器),便利店一般都會舉行一些活動,例如積滿15次消費金額在20元以上,送毛絨公仔一隻。但是客戶(瀏覽器)特別多,便利店不可能每個都記住,於是便利店就製作一個卡片(Cookie)交給客戶。之後每次客戶來買東西的人時候就把卡片提交給便利店(web服務器),於是便利店就知道你目前的狀態信息,這樣就做到了跟蹤會話。
      從上面可以看出,Cookie是由瀏覽器管理的,web服務器將數據交給瀏覽器,瀏覽器可以把數據保存起來,等到下次訪問的時候自動提交。
      
如上圖中,在整個交互中的流程如下:
      第一步:瀏覽器發送信息(例如:用戶名密碼)提交給服務器。
      第二步:服務器接收到之後,發送一個命令給瀏覽器,告訴他你要把相關的信息存到cookie裏面。
      第三步:如果是零時性的就存於瀏覽器內存中,重啓瀏覽器信息就消失了。
      第四步:如果是需要存很久,例如兩週之內自動登陸這種需求,則將信息存於硬盤上。
      第五步:當在此訪問瀏覽器時,則自動將cookie發送給服務器。

3、各瀏覽器都把Cookie放到了哪裏

以Windows7爲例:
      IE瀏覽器:%APPDATA%\Microsoft\Windows\Cookies\ 目錄中的xxx.txt文件 (IE瀏覽器分開存放的)。
      火狐瀏覽器:%APPDATA%\Mozilla\Firefox\Profiles\ 目錄中的xxx.default目錄,名爲cookies.sqlite的文件。
      谷歌瀏覽器:%LOCALAPPDATA%\Google\Chrome\User Data\Default\ 目錄中,名爲Cookies的文件。
在IE瀏覽器中,IE將各個站點的Cookie分別保存爲一個XXX.txt這樣的純文本文件(文件個數可能很多,但文件大小都較小);而Firefox和Chrome是將所有的Cookie都保存在一個文件中(文件大小較大),該文件的格式爲SQLite3數據庫格式的文件。

4、實際開發項目中如何使用代碼操作Cookie

在Java中,Web項目提供了一個類:javax.servlet.http.Cookie 該類就與Cookie對應。下面給出Servlet中操作Cookie示例代碼:
package com.csdn.cas;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 獲取請求參數
		String userName = req.getParameter("userName");
		String passwd = req.getParameter("passwd");
		
		// 創建cookie對象
		Cookie userInfoCookie = new Cookie("userInfo", userName + ":" + passwd);
		
		// 返回給瀏覽器的數據中添加cookie信息
		resp.addCookie(userInfoCookie);
	}
}
上面就是簡單的操作cookie的代碼,我們將這個servlet部署到tomcat中,使用谷歌,並觀察相關的cookie信息(使用F12,有調試工具)。
      
觀察第一次的訪問情況,在響應頭裏面多出來一個Set-Cookie:的東東,瀏覽器就根據這個這個cookie信息保留了下來。
      
當再一次訪問這個網站時,則請求頭中多了第一次返回的Cookie信息,響應中之所以還有Set-Cookie,是因爲我們訪問的是同一個代碼。
如果你把瀏覽器關了,在訪問這個地址,那麼請求中是不會帶有cookie信息的,因爲瀏覽器只是零時性的將數據保存在內存中。如果需要保存在硬盤上則需要特殊處理。

5、Cookie的特性

5.1、Cookie不能跨域

      例如:你訪問www.baidu.com,百度給你發了一個cookie,你在訪問www.google.com你是不能把百度發給你的cookie帶到谷歌的服務器上的。
      需要特別注意的是:images.google.com與www.google.com同屬於google,但是他們的域名不一樣,二者同樣也不能操作彼此的Cookie,瀏覽器在訪問的時候,會根據訪問地址自動攜帶相關域名的cookie過去。
      注意:有時候你會發現有些特殊的網站,類似你登錄了www.google.com但是你當問images.google.com時,www.google.com的cookie被帶到了images.google.com,這是因爲做了特殊的處理,下面會詳細說。

5.2、其他相關特性請參照博客

      

6、利用Cookie的特性實現單點登錄的原理


上圖就是一個利用cookie做單點登錄的原理圖,案例爲用戶dgh(密碼123)登錄www.qiandu.com,之後又登錄mail.qiandu.com。流程如下:
      第一步:用戶輸入用戶名/密碼(dgh/123)登錄到www.qiandu.com。
      第二步:www.qiandu.com處理登錄邏輯,並且將用戶信息通過cookie的方式返回到客戶端(最好加密用戶信息)。
      第三步:用戶訪問mail.qiandu.com,瀏覽器自動將用戶信息攜帶到mail.qiandu.com,通過過濾器(filter)處理用戶的登錄請求。
      第四步:過濾器從cookie中獲取用戶信息,登錄,返回用戶訪問界面。這樣用戶就只登錄一次,就訪問了兩個網站了。

7、Cookie實現單點登錄的代碼實現

1、首先是一個登錄的Servlet,簡單實現,只要用戶名密碼相同則登錄成功,並將信息寫到cookie中。
package com.csdn.cas;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 獲取請求參數
		String userName = req.getParameter("userName");
		String passwd = req.getParameter("passwd");
		
		resp.setCharacterEncoding("UTF-8");
		resp.setContentType("text/html;charset=UTF-8");
		
		HttpSession session = req.getSession();
		// 只有用戶名與密碼相同,則登錄成功
		if(userName.equals(passwd)){
			// 創建cookie對象
			Cookie userInfoCookie = new Cookie("userInfo", userName + ":" + passwd);
			
			// 這裏很重要,不設置無法誇子域 這裏最好以 .開頭,例如.qiandu.com
			// 谷歌瀏覽器自動給他添加了.
			userInfoCookie.setDomain("qiandu.com");
			
			// 返回給瀏覽器的數據中添加cookie信息
			resp.addCookie(userInfoCookie);
			
			session.setAttribute("userName", userName + ",登錄成功");
			
		}else {
			session.setAttribute("userName", userName + ",登錄失敗");
		}
		
		req.getRequestDispatcher("/index.jsp").forward(req, resp);
	}
}

2、寫一個Filter,過濾處理所有的請求,如果是登錄請求則到登錄頁,否則嘗試登陸。
package com.csdn.cas;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException { }
	 
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		HttpServletRequest req = (HttpServletRequest)request ;
		HttpServletResponse resp = (HttpServletResponse) response ;
		Cookie[] cookies = req.getCookies();
		
		resp.setCharacterEncoding("UTF-8");
		resp.setContentType("text/html;charset=UTF-8");
		
		HttpSession session = req.getSession();
		Object userInfo = session.getAttribute("userName");
		if(userInfo == null){ // 沒登錄
			if(cookies != null){ // 有cookie
				for(Cookie cookie : cookies){
					if("userInfo".equals(cookie.getName())){
						String[] value = cookie.getValue().split(":");
						String userName = value[0];
						String passwd = value[1];
						// 只有用戶名與密碼相同,則登錄成功
						if(userName.equals(passwd)){
							// 創建cookie對象
							session.setAttribute("userName", userName + ",從filter登錄成功");
						}else {
							session.setAttribute("userName", userName + ",從filter登錄失敗");
						}
					}
				}
			} else {
				// 這裏應該跳轉到登錄頁面
			}
		}
		
		
		chain.doFilter(request, response);
	}
	
	@Override
	public void destroy() {	}
}

3、index.jsp頁面就展示登錄用戶用戶名,如果沒有登錄就顯示null
<html>
<body>
	<h1>Hello !</h1>
	<h2><%=session.getAttribute("userName")%>
	</h2>
</body>
</html>

注意:
      上面說過cookie是不能跨域的,通過特殊設置setDomain()可以做到誇同一個大域下的兩個子域。例如,www.qiandu.com與mail.qiandu.com這是兩個域,但是他們都是qiandu.com下的兩個子域,則可以通過設置cookie.setDomain("qiandu.com")從而達到誇域。但是對於www.baidu.com與www.google.com就沒得辦法了。

8、測試Cookie的單點登錄

修改windows的hosts文件,添加如下內容:



測試步驟:
第一步:啓動服務器,通過www.qiandu.com訪問服務,顯示登陸失敗


第二步:通過get方式登錄:http://www.qiandu.com/login?userName=dgh&passwd=dgh


第三步:登錄http://mail.qiandu.com,發現第一次登陸mail.qiandu.com用戶名密碼就被cookie帶過來了。


然後頁面顯示:


注意:爲什麼www.qiandu.com的cookie能帶到mail.qiandu.com呢?我們從瀏覽器中查看cookie信息即可得知,域是www.qiandu.com與mail.qiandu.com的公共部分,所以瀏覽器認爲,他們是一起的,cookie交流是安全的。而且我代碼中寫的是qiandu.com,瀏覽器自動在前面加了一個點
      




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章