欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Session与Cookie底层实现原理

程序员文章站 2022-07-13 10:52:54
...

一、会话管理入门

  1. 生活中回会话
    我:小王,你会跳舞么?
    小王:会,怎么了?
    我:我要表演节目,你教教我吧。
    小王:没问题,请我吃顿饭。
    我:OK。
    。。。。。。。。
    在这次生活中的会话产生通话记录(会话数据)
  2. 转件中的会话
    一次会话: 打开浏览器 -> 访问一些服务器内容 -> 关闭浏览器
    1. 登录场景:
      打开浏览器 -> 浏览到登陆页面 -> 输入用户名和密码 -> 访问到用户主页(显示用户名)
      修改密码(输入原密码)
      修改收货地址
    2. 购物场景:
      打开浏览器 -> 浏览商品列表 -> 加入购物车(把商品信息保存下来) -> 关闭浏览器
      重新打开浏览器-> 直接进入购物车 -> 查看到上次加入购物车的商品 -> 下订单 -> 支付
      -----Cookie存储,存放在客户端,不能跨浏览器。
      现在企业的购物车数据都不是存放在本地,因为不太安全,都是存在数据库中的一张表,如果数据量比较大会用到缓存。
    3. 移动APP接口的会话管理: 管理浏览器客户端和服务器端之间会话过程中产生的会话数据。
      域对象: 实现资源之间的数据共享。
      request域对象,只能在转发有效。
      context域对象,保存在服务器端上。
  3. 会话技术
    Cookie技术:服务器与客户端资源保存,保存在客户端。
    Session技术:是保存在服务器端上的,存放在内存里面,客户端与服务器端之间通讯使用SessionId(Token)。

二、Cookie技术

  1. 特点
    Cookie技术:会话数据保存在浏览器客户端。
  2. Cookie技术核心
    Cookie类:用于存储会话数据
    1. 构造Cookie对象
      Cookie(java.lang.String name, java.lang.String value)
    2. 设置cookie
      void setPath(java.lang.String uri) :设置cookie的有效访问路径
      void setMaxAge(int expiry) : 设置cookie的有效时间
      void setValue(java.lang.String newValue) :设置cookie的值
    3. 发送cookie到浏览器端保存
      void response.addCookie(Cookie cookie) : 发送cookie
    4. 服务器接收cookie
      Cookie[] request.getCookies() : 接收cookie
  3. Cookie原理
    1. 服务器创建cookie对象,把会话数据存储到cookie对象中。
      new Cookie(“name”,“value”);
    2. 服务器发送cookie信息到浏览器
      response.addCookie(cookie);
      举例: set-cookie: name=eric (隐藏发送了一个set-cookie名称的响应头)
    3. 浏览器得到服务器发送的cookie,然后保存在浏览器端。
    4. 浏览器在下次访问服务器时,会带着cookie信息
      举例: cookie: name=eric (隐藏带着一个叫cookie名称的请求头)
    5. 服务器接收到浏览器带来的cookie信息
      request.getCookies();
  4. Cookie的细节
    1. void setPath(java.lang.String uri) :设置cookie的有效访问路径。有效路径指的是cookie的有效路径保存在哪里,那么浏览器在有效路径下访问服务器时就会带着cookie信息,否则不带cookie信息。
    2. void setMaxAge(int expiry) : 设置cookie的有效时间。
      正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。
      负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了!!
      零:表示删除同名的cookie数据。
    3. Cookie数据类型只能保存非中文字符串类型的。可以保存多个cookie,但是浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
  5. 代码实现
    1. 创建Cookie:
    package chauncy.cookie;
    	
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    
    /**   
     * @classDesc: 功能描述(创建Cookie)  
     * @author: ChauncyWang
     * @createTime: 2019年4月2日 上午10:16:06   
     * @version: 1.0  
     */  
    @WebServlet("/CreateCookieServlet")
    public class CreateCookieServlet extends HttpServlet{
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    		//key value 自定义Cookie
    		Cookie cookie = new Cookie("userName", "123456");
    		//如果是负数,浏览器关闭失效,如果是正数,以秒为单位进行保存。
    		cookie.setMaxAge(60*60*24);//cookie保存一天,没有任何一家公司能永久保存登录,说永久只不过把值设置的比较大。
    		//将Cookie发送给客户端
    		resp.addCookie(cookie);
    		System.out.println("创建Cookie成功!");
    	}
    }
    
    2.获取Cookie:
    package chauncy.cookie;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    
    /** 
     * Cookie默认浏览器关闭失效  
     * @classDesc: 功能描述(获取Cookie)  
     * @author: ChauncyWang
     * @createTime: 2019年4月2日 上午10:58:22   
     * @version: 1.0  
     */  
    @WebServlet("/GetCookieServlet")
    public class GetCookieServlet extends HttpServlet{
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    		//获取所有的Cookie信息
    		Cookie[] cookies = req.getCookies();
    		if(cookies != null){
    			for (Cookie cookie : cookies) {
    				System.out.println(cookie.getName()+"---"+cookie.getValue());
    			}
    		}else{
    			System.out.println("cookie为null");
    		}
    	}
    }
    
    3.显示用户最后一次访问时间:
    package chauncy.cookie;
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringUtils;
    
    /**   
     * @classDesc: 功能描述(显示用户最后一次访问的时间)  
     * @author: ChauncyWang
     * @createTime: 2019年4月2日 下午2:09:19   
     * @version: 1.0  
     */ 
    @WebServlet("/LastAccessTime")
    public class LastAccessTime extends HttpServlet{
    	private static final String COOKIE_KEY_LASTACCESSTIME="COOKIE_KEY_LASTACCESSTIME";
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    		resp.setContentType("text/html;charset=utf-8");//防止浏览器产生乱码
    		//1.获取Cookie信息
    		Cookie[] cookies = req.getCookies();
    		String lastAccessTime=null;
    		if(cookies!=null){
    			for(Cookie cookie:cookies){
    				String name = cookie.getName();
    				if(COOKIE_KEY_LASTACCESSTIME.equals(name)){
    					lastAccessTime=cookie.getValue();
    					break;
    				}
    			}
    		}
    		//2.如果Cookie信息没有数据,说明第一次访问,有数据获取上一次Cookie的值。
    		if(StringUtils.isEmpty(lastAccessTime)){
    			resp.getWriter().write("您是首次访问!");
    		}else{
    			resp.getWriter().write("您上次访问时间为:"+lastAccessTime);
    		}
    		//3.现在访问的登录时间存放在Cookie中
    		//当前访问时间
    		String currentTime = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss").format(new Date());
    		//创建cookie将当前时间作为cookie保存到浏览器
    		Cookie cookie = new Cookie(COOKIE_KEY_LASTACCESSTIME,currentTime);
    		cookie.setMaxAge(60*60*24);
    		//将cookie发送给客户端
    		resp.addCookie(cookie);
    	}
    }
    
  6. Cookie的局限:
    1. Cookie只能存字符串类型,不能保存对象。
    2. 只能存非中文。
    3. 1个Cookie的容量不超过4KB。

三、Session技术

  1. 特点:
    会话数据保存在服务器端。(内存中)

  2. Session技术核心:
    HttpSession类:用于保存会话数据

    1. 创建或得到session对象
      HttpSession getSession()
      HttpSession getSession(boolean create)
    2. 设置session对象
      void setMaxInactiveInterval(int interval) : 设置session的有效时间
      void invalidate() : 销毁session对象
      java.lang.String getId() : 得到session编号
    3. 保存会话数据到session对象
      void setAttribute(java.lang.String name, java.lang.Object value) : 保存数据
      java.lang.Object getAttribute(java.lang.String name) : 获取数据
      void removeAttribute(java.lang.String name) : 清除数据
  3. Session原理
    服务器创建完Session,会将SessionId通过响应头方式返回给客户端。客户端将sessionId保存在本地硬盘,再次请求的时候,将sessionId通过请求头的方式传给服务器端。
    Session的值是存放在服务器端内存中,只要没有达到失效要求不会失效,关闭浏览器,只是SessionId失效,但是Session没有失效。
    移动APP的登录也是此种方式。
    具体步骤:

    1. 第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID,new HttpSession();
    2. 把JSESSIONID作为Cookie的值发送给浏览器保存,Cookie cookie = new Cookie(“JSESSIONID”, sessionID);response.addCookie(cookie);
    3. 第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器。
    4. 服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。
    5. 如果找到对应编号的session对象,直接返回该对象。
    6. 如果找不到对应编号的session对象,创建新的session对象,继续走1的流程。

    总结:通过JSESSION的cookie值在服务器找session对象。

  4. Sesson细节

    1. java.lang.String getId():得到session编号。

    2. 两个getSession方法:
      getSession(true) / getSession():创建或得到session对象。没有匹配的session编号,自动创建新的session对象。
      getSession(false):得到session对象。没有匹配的session编号,返回null。

    3. void setMaxInactiveInterval(int interval) : 设置session的有效时间。
      session对象销毁时间:

      1. 默认情况30分服务器自动回收。
      2. 修改session回收时间。
      3. 全局修改session有效时间。
        <!-- 修改session全局有效时间:分钟 -->
        <session-config>
        	<session-timeout>1</session-timeout>
        </session-config>
        
      4. 手动销毁session对象
        void invalidate():销毁sessio。n对象
    4. 如何避免浏览器的JSESSIONID的cookie随着浏览器关闭而丢失的问题。

      /**
      * 手动发送一个硬盘保存的cookie给浏览器
      */
      Cookie c = new Cookie("JSESSIONID",session.getId());
      c.setMaxAge(60*60);
      response.addCookie(c);
      
  5. 代码实现创建、调用Session

    1. 创建Session
      package chauncy.session;
      
      import java.io.IOException;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import javax.servlet.http.HttpSession;
      
      /**   
       * @classDesc: 功能描述(创建Session)  
       * @author: ChauncyWang
       * @createTime: 2019年4月2日 下午4:14:34   
       * @version: 1.0  
       */
      @WebServlet("/CreateSessionServlet")
      public class CreateSessionServlet extends HttpServlet{
      	@Override
      	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      		//getSession()无参数形式默认为true,如果没有Session,就会创建一个session,如果参数为false,如果没有找到Session,就不会创建session。
      		HttpSession session = req.getSession();
      		session.setAttribute("userName", "ChauncyWang");
      		System.out.println("保存session成功!sessionId:"+session.getId());
      	}
      }
      
    2. 获取Session
      package chauncy.session;
      
      import java.io.IOException;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import javax.servlet.http.HttpSession;
      
      /**   
       * @classDesc: 功能描述(获取Session)  
       * @author: ChauncyWang
       * @createTime: 2019年4月2日 下午4:24:31   
       * @version: 1.0  
       */  
      @WebServlet("/GetSessionServlet")
      public class GetSessionServlet extends HttpServlet{
      	@Override
      	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      		//获取session使用getSession(false)参数要为false
      		HttpSession session = req.getSession(false);
      		if(session != null){
      			String userName = (String) session.getAttribute("userName");
      			System.out.println("GetSessionServlet()----userName:"+userName);
      			System.out.println("GetSessionServlet()----sessionId:"+session.getId());
      		}else{
      			System.out.println("没有找到任何结果");
      		}
      	}
      
      }
      

四、自定义缓存

  1. 定义缓存实体类:
    package chauncy.customcache;
    
    /**   
     * @classDesc: 功能描述(缓存实体类)  
     * @author: ChauncyWang
     * @createTime: 2019年4月3日 上午10:32:52   
     * @version: 1.0  
     */  
    public class Cache {
    	//SessionId
    	private String sessionId;
    	//键
    	private String key;
    	//值
    	private Object value;
    	//有效期
    	private Long timout;
    	
    	
    	public String getSessionId() {
    		return sessionId;
    	}
    	public void setSessionId(String sessionId) {
    		this.sessionId = sessionId;
    	}
    	public String getKey() {
    		return key;
    	}
    	public void setKey(String key) {
    		this.key = key;
    	}
    	public Object getValue() {
    		return value;
    	}
    	public void setValue(Object value) {
    		this.value = value;
    	}
    	public Long getTimout() {
    		return timout;
    	}
    	public void setTimout(Long timout) {
    		this.timout = timout;
    	}
    	
    }
    
  2. 使用UUID实现Token工具类,生成sessionId:
    package chauncy.customsession;
    
    import java.util.UUID;
    
    /**
     * Token其实就是一个令牌,随机生成,有有效期,不能重重复。
     * Token类似于SessionId;   
     * @classDesc: 功能描述(使用UUID实现Token工具类)  
     * @author: ChauncyWang
     * @createTime: 2019年4月3日 下午3:24:40   
     * @version: 1.0  
     */  
    public class TokenUtils {
    	static public String getToken(){
    		return UUID.randomUUID().toString();
    	}
    	public static void main(String[] args) {
    		System.out.println(TokenUtils.getToken());
    	}
    }
    
  3. 定义缓存类:
    package chauncy.customcache;
    
    import java.nio.channels.NetworkChannel;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    import chauncy.customsession.TokenUtils;
    
    /**
     * @classDesc: 功能描述(提供缓存的API)
     * @author: ChauncyWang
     * @createTime: 2019年4月3日 上午10:38:13
     * @version: 1.0
     */
    public class CacheManager {
    	private static final CacheManager cacheManager=new CacheManager();
    	
    	private CacheManager(){
    		
    	}
    	
    	public static CacheManager getCacheManager(){
    		return cacheManager;
    	}
    	
    	// 存放缓存数据
    	private Map<String, Cache> chacheMap = new HashMap<String, Cache>();
    
    	public String put(String key, Object value) {
    		String sessionId = put(key, value, null);
    		return sessionId;
    	}
    
    	public synchronized String put(String key, Object value, Long timeout) {
    		Cache cache = new Cache();
    		//生成SessionId
    		String sessionId=TokenUtils.getToken();
    		cache.setSessionId(sessionId);
    		cache.setKey(key);
    		cache.setValue(value);
    		if (timeout != null) {
    			//保存的是整个毫秒数
    			cache.setTimout(System.currentTimeMillis()+timeout);
    		}
    		chacheMap.put(key, cache);
    		return sessionId;
    	}
    	
    	/**
    	 * 
    	 * @methodDesc: 功能描述(删除api)  
    	 * @author: ChauncyWang
    	 * @param: @param key   
    	 * @createTime: 2019年4月3日 上午11:00:10   
    	 * @returnType: void
    	 */
    	public synchronized void del(String key) {
    		System.out.println("key:"+key+"被删除");
    		chacheMap.remove(key);	
    	}
    	
    	/**
    	 * 
    	 * @methodDesc: 功能描述(使用key查询缓存)  
    	 * @author: ChauncyWang
    	 * @param: @param key
    	 * @param: @return   
    	 * @createTime: 2019年4月3日 上午10:59:49   
    	 * @returnType: Object
    	 */
    	public synchronized Object get(String key){
    		Cache cache = chacheMap.get(key);
    		if(cache != null){
    			return cache;
    		}
    		return null;
    	}
    	
    	/**
    	 * 
    	 * @methodDesc: 功能描述(定时检查删除有效期的值)  
    	 * @author: ChauncyWang
    	 * @param:    
    	 * @createTime: 2019年4月3日 上午11:10:16   
    	 * @returnType: void
    	 */
    	public synchronized void checkValidityData(){
    		for (String key : chacheMap.keySet()) {
    			Cache cache = chacheMap.get(key);
    			if(cache == null){
    				break;
    			}
    			//检查有效期,获取缓存的毫秒数。
    			Long timout = cache.getTimout();
    			//计算时间差,获取当前时间毫秒数
    			Long currentTime = System.currentTimeMillis();
    			//说明已经过时
    			if((currentTime-timout)>0){
    				del(key);
    			}
    		}
    	}
    
    	public static void main(String[] args) {
    		final CacheManager cacheManager = new CacheManager();
    		//如果设置时间,开启一个线程,检查有效期
    		cacheManager.put("userName", "123",5000l);
    		System.out.println("保存成功。。。");
    		//开启一个线程定期检查刷新数据
    		// 入参为线程池大小,
    		ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
    		// schedule执行定时任务线程池,第一个参数需要创建Runnable接口对象,第二、三个参数表示多少个单位时间执行run方法。
    		newScheduledThreadPool.schedule(new Runnable() {
    			public void run() {
    				cacheManager.checkValidityData();
    			}
    		}, 5000, TimeUnit.MILLISECONDS);
    		try {
    			Thread.sleep(5000);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		Cache cache = (Cache) cacheManager.get("userName");
    		System.out.println("userName:"+cache.getValue());	
    	}
    }
    

五、自定义Session

  1. 实现自定义Session,需要结合自定义缓存共同查看。
    package chauncy.customsession;
    
    import chauncy.customcache.Cache;
    import chauncy.customcache.CacheManager;
    
    public class SessionUtil {
    	
    	private CacheManager cacheManager;
    	
    	/**
    	 * 
    	 * @methodDesc: 功能描述(初始化cacheManager)  
    	 * @author: ChauncyWang
    	 * @param:    
    	 * @createTime: 2019年4月3日 下午3:03:35   
    	 * @returnType: void
    	 */
    	public SessionUtil() {
    		cacheManager=CacheManager.getCacheManager();
    	}
    	
    	/**
    	 * 
    	 * @methodDesc: 功能描述(新增一个Session,返回一个SessionId)  
    	 * @author: ChauncyWang
    	 * @param: @param key
    	 * @param: @param value
    	 * @param: @return   
    	 * @createTime: 2019年4月3日 下午3:11:34   
    	 * @returnType: String
    	 */
    	public String setAttribute(String key,Object value){
    		//生成SessionId
    		String sessionId=cacheManager.put(key, value);
    		return sessionId;
    	}
    	//通过key值获取缓存对象Cache,缓存对象包含sessionId和value值
    	public Object getAttribute(String key){
    		return cacheManager.get(key);
    	}
    	
    	public static void main(String[] args) {
    		SessionUtil sessionUtil = new SessionUtil();
    		String setAttribute = sessionUtil.setAttribute("userName", "ChauncyWang");
    		System.out.println("CreateSessionId:"+setAttribute);
    		Cache cache = (Cache) sessionUtil.getAttribute("userName");
    		System.out.printf("CacheEntity--->sessionId:"+cache.getSessionId()+"\tkey:"+cache.getKey()+"\tvalue:"+cache.getValue());
    	}
    }
    

六、自定义Token

  1. 什么是token?
    token其实就是一个令牌,具有随机性,类似于sessionId。
    在对接一些第三方平台的时候,为了能够保证数据安全性,通常会使用一些令牌进行交互。
    例如: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
  2. 如何自定义token?
    token生成规则,只要保证token生成一个不重复的唯一字符串即可。
    使用jdk自带的uuid生成规则。
  3. 什么是UUID?
    UUID含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准,也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。
    UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过*控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的 UUID。
    在这样的情况下,就不需考虑数据库建立时的名称重复问题。目前最广泛应用的 UUID,即是微软的 Microsoft’s Globally Unique Identifiers (GUIDs),而其他重要的应用,则有 Linux ext2/ext3 档案系统、LUKS 加密分割区、GNOME、KDE、Mac OS X 等等。
  4. UUID组成:
    UUID保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。
    UUID由以下几部分的组合:
    (1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
    (2)时钟序列。
    (3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
    UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。在ColdFusion中可以用CreateUUID()函数很简单地生成UUID,
    其格式为:xxxxxxxx-xxxx- xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。而标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12);
  5. UUID代码:
    UUID.randomUUID().toString();
    

七、表单重复提交解决方案(防止Http重复提交)

表单重复提交 常用两个解决办法
通过前端解决
通过后端解决