Session与Cookie底层实现原理
程序员文章站
2022-07-13 10:52:54
...
一、会话管理入门
- 生活中回会话
我:小王,你会跳舞么?
小王:会,怎么了?
我:我要表演节目,你教教我吧。
小王:没问题,请我吃顿饭。
我:OK。
。。。。。。。。
在这次生活中的会话产生通话记录(会话数据) - 转件中的会话
一次会话: 打开浏览器 -> 访问一些服务器内容 -> 关闭浏览器- 登录场景:
打开浏览器 -> 浏览到登陆页面 -> 输入用户名和密码 -> 访问到用户主页(显示用户名)
修改密码(输入原密码)
修改收货地址
… - 购物场景:
打开浏览器 -> 浏览商品列表 -> 加入购物车(把商品信息保存下来) -> 关闭浏览器
重新打开浏览器-> 直接进入购物车 -> 查看到上次加入购物车的商品 -> 下订单 -> 支付
-----Cookie存储,存放在客户端,不能跨浏览器。
现在企业的购物车数据都不是存放在本地,因为不太安全,都是存在数据库中的一张表,如果数据量比较大会用到缓存。 - 移动APP接口的会话管理: 管理浏览器客户端和服务器端之间会话过程中产生的会话数据。
域对象: 实现资源之间的数据共享。
request域对象,只能在转发有效。
context域对象,保存在服务器端上。
- 登录场景:
- 会话技术
Cookie技术:服务器与客户端资源保存,保存在客户端。
Session技术:是保存在服务器端上的,存放在内存里面,客户端与服务器端之间通讯使用SessionId(Token)。
二、Cookie技术
- 特点
Cookie技术:会话数据保存在浏览器客户端。 - Cookie技术核心
Cookie类:用于存储会话数据- 构造Cookie对象
Cookie(java.lang.String name, java.lang.String value) - 设置cookie
void setPath(java.lang.String uri) :设置cookie的有效访问路径
void setMaxAge(int expiry) : 设置cookie的有效时间
void setValue(java.lang.String newValue) :设置cookie的值 - 发送cookie到浏览器端保存
void response.addCookie(Cookie cookie) : 发送cookie - 服务器接收cookie
Cookie[] request.getCookies() : 接收cookie
- 构造Cookie对象
- Cookie原理
- 服务器创建cookie对象,把会话数据存储到cookie对象中。
new Cookie(“name”,“value”); - 服务器发送cookie信息到浏览器
response.addCookie(cookie);
举例: set-cookie: name=eric (隐藏发送了一个set-cookie名称的响应头) - 浏览器得到服务器发送的cookie,然后保存在浏览器端。
- 浏览器在下次访问服务器时,会带着cookie信息
举例: cookie: name=eric (隐藏带着一个叫cookie名称的请求头) - 服务器接收到浏览器带来的cookie信息
request.getCookies();
- 服务器创建cookie对象,把会话数据存储到cookie对象中。
- Cookie的细节
- void setPath(java.lang.String uri) :设置cookie的有效访问路径。有效路径指的是cookie的有效路径保存在哪里,那么浏览器在有效路径下访问服务器时就会带着cookie信息,否则不带cookie信息。
- void setMaxAge(int expiry) : 设置cookie的有效时间。
正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。
负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了!!
零:表示删除同名的cookie数据。 - Cookie数据类型只能保存非中文字符串类型的。可以保存多个cookie,但是浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
- 代码实现
1. 创建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; /** * @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成功!"); } }
3.显示用户最后一次访问时间: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"); } } }
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); } }
- Cookie的局限:
- Cookie只能存字符串类型,不能保存对象。
- 只能存非中文。
- 1个Cookie的容量不超过4KB。
三、Session技术
-
特点:
会话数据保存在服务器端。(内存中) -
Session技术核心:
HttpSession类:用于保存会话数据- 创建或得到session对象
HttpSession getSession()
HttpSession getSession(boolean create) - 设置session对象
void setMaxInactiveInterval(int interval) : 设置session的有效时间
void invalidate() : 销毁session对象
java.lang.String getId() : 得到session编号 - 保存会话数据到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) : 清除数据
- 创建或得到session对象
-
Session原理
服务器创建完Session,会将SessionId通过响应头方式返回给客户端。客户端将sessionId保存在本地硬盘,再次请求的时候,将sessionId通过请求头的方式传给服务器端。
Session的值是存放在服务器端内存中,只要没有达到失效要求不会失效,关闭浏览器,只是SessionId失效,但是Session没有失效。
移动APP的登录也是此种方式。
具体步骤:- 第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID,new HttpSession();
- 把JSESSIONID作为Cookie的值发送给浏览器保存,Cookie cookie = new Cookie(“JSESSIONID”, sessionID);response.addCookie(cookie);
- 第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器。
- 服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。
- 如果找到对应编号的session对象,直接返回该对象。
- 如果找不到对应编号的session对象,创建新的session对象,继续走1的流程。
总结:通过JSESSION的cookie值在服务器找session对象。
-
Sesson细节
-
java.lang.String getId():得到session编号。
-
两个getSession方法:
getSession(true) / getSession():创建或得到session对象。没有匹配的session编号,自动创建新的session对象。
getSession(false):得到session对象。没有匹配的session编号,返回null。 -
void setMaxInactiveInterval(int interval) : 设置session的有效时间。
session对象销毁时间:- 默认情况30分服务器自动回收。
- 修改session回收时间。
- 全局修改session有效时间。
<!-- 修改session全局有效时间:分钟 --> <session-config> <session-timeout>1</session-timeout> </session-config>
- 手动销毁session对象
void invalidate():销毁sessio。n对象
-
如何避免浏览器的JSESSIONID的cookie随着浏览器关闭而丢失的问题。
/** * 手动发送一个硬盘保存的cookie给浏览器 */ Cookie c = new Cookie("JSESSIONID",session.getId()); c.setMaxAge(60*60); response.addCookie(c);
-
-
代码实现创建、调用Session
- 创建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()); } }
- 获取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("没有找到任何结果"); } } }
- 创建Session
四、自定义缓存
- 定义缓存实体类:
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; } }
- 使用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()); } }
- 定义缓存类:
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
- 实现自定义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
- 什么是token?
token其实就是一个令牌,具有随机性,类似于sessionId。
在对接一些第三方平台的时候,为了能够保证数据安全性,通常会使用一些令牌进行交互。
例如: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183 - 如何自定义token?
token生成规则,只要保证token生成一个不重复的唯一字符串即可。
使用jdk自带的uuid生成规则。 - 什么是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 等等。 - 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); - UUID代码:
UUID.randomUUID().toString();
七、表单重复提交解决方案(防止Http重复提交)
表单重复提交 常用两个解决办法
通过前端解决
通过后端解决
上一篇: postgresql 性能参数调优
下一篇: TiDB集群优化方案