【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,浏览器自动在前面加了一个点
      




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