当前位置: 首页 > 科技观察

一文彻底搞懂Cookie、Session、Token到底是什么

时间:2023-03-14 21:39:51 科技观察

这篇文章彻底了解什么是Cookie、Session、Token。叔叔:什么是马?夏洛特:马冬梅。叔叔:没什么?夏洛特:马冬梅。叔叔:那匹马呢?夏洛特:好的,先生,您应该先冷静下来。在理解这三个概念之前,我们首先要明白HTTP是一种无状态的Web服务器。什么是无国籍?就像上面夏洛特烦恼中的经典对话场景一样,一个对话完成后,下一个对话完全不知道上一个对话发生了什么。如果只是用来管理web服务器中的静态文件,那么不管对方是谁,直接从磁盘中读取文件发送出去即可。但是随着网络的不断发展,比如电子商务中的购物车,只有记住了用户的身份,才能进行接下来一系列的动作。所以在这一点上我们需要我们的无状态服务器记住一些东西。那么网络服务器如何记住一些东西呢?既然web服务器记不住东西,那我们就想办法在外部记住,相当于服务器给每个客户端都贴上一张小纸条。上面记录了服务器返回给我们的一些信息。然后服务器看到这个小纸条就知道我们是谁了。那么是谁生成了cookie?Cookie由服务器生成。接下来,我们来描述一下Cookie的生成过程。当浏览器第一次访问服务器时,此时服务器肯定不知道他的身份,所以创建一个唯一的身份数据,格式为key=value,放入Set-Cookie字段,一起发送给浏览器与响应消息。浏览器看到Set-Cookie字段就知道这是服务器给的标识,所以保存起来,请求的时候会自动把这个key=value值放到Cookie字段中发送给服务器下一次。服务器收到请求报文后,发现Cookie字段中有一个值,可以根据这个值来识别用户的身份,进而提供个性化的服务。下面我们用代码来演示下服务器是如何生成的。我们自己搭建后台服务器。这里我使用SpringBoot搭建,写入SpringMVC的代码如下。@RequestMapping("/testCookies")publicStringcookies(HttpServletResponseresponse){response.addCookie(newCookie("testUser","xxxx"));return"cookies";}项目启动后,我们进入路径http://localhost:8005/testCookies,然后查看发送的请求。可以看到下图是我们第一次访问服务器时发送的请求,可以看到服务器返回的响应中有一个Set-Cookie字段。而里面的key=value值正是我们服务器中设置的值。接下来,我们再次刷新页面,可以看到请求体中已经设置了Cookie字段,我们的值也被带过来了。这样服务器就可以根据cookie中的值记住我们的信息了。另一个请求呢?Cookie也会带过来吗?接下来我们输入路径http://localhost:8005请求。我们可以看到Cookie字段还是被接管了。那么浏览器cookie存储在哪里呢?如果您使用的是Chrome浏览器,则可以按照以下步骤操作。在计算机上打开Chrome。右上角一次性点击更多图标->底部设置,点击高级,在隐私设置和安全下,点击网站设置,点击Cookies->查看所有Cookies和网站数据,然后根据域名搜索.管理cookie数据。因此,是浏览器为您管理cookie数据。如果此时切换到其他浏览器如Firefox,由于刚才在Chrome中存储了cookie,服务器又被阻塞了。如果你不知道你是谁,就再贴一下Firefox的小纸条。cookies中的参数设置说到这里大家应该知道cookies是服务器委托浏览器存储在客户端的一些数据,这些数据通常记录了用户的关键身份信息。因此,cookie需要通过一些其他方式进行保护,以防止泄露或被盗。这些手段就是cookies的属性。参数名作为后端设置方法Max-Age设置cookie的过期时间,单位秒cookie.setMaxAge(10)Domain指定cookie所属域名cookie.setDomain("")Path指定路径cookie属于哪个cookie.setPath("");HttpOnly告诉浏览器,这个cookie只能通过浏览器的Http协议传输,禁止其他访问cookie。setHttpOnly(true)Secure告诉浏览器这个cookie只能在Https安全协议中传输,如果是Http则禁止传输cookie.setSecure(true)下面简单演示一下这几个参数的用法和现象。Path设置为cookie.setPath("/testCookies"),然后我们访问http://localhost:8005/testCookies,可以看到左边的路径和我们指定的一样,所以请求头中出现了Cookie,然后我们访问http://localhost:8005,发现没有Cookie字段,这是Path控制的路径。Domain设置为cookie.setDomain("localhost"),然后我们访问http://localhost:8005/testCookies发现下图左边有一个cookie字段,但是我们访问http://172.16.42.81:8005/testCookies,看下图右侧可以看到没有cookie的字段。这是发送cookie的Domain控制的域名。下面几个参数就不一一演示了。相信这里大家应该对cookies有所了解。Session>Cookie保存在客户端,Session保存在服务器端,客户端只保存SessionId就可以了。我们了解什么是cookie。既然浏览器已经通过cookies实现了有状态的需求,为什么还要再来呢?会话呢?这里我们想象一下,如果cookie中保存了一些账户信息,一旦信息被拦截,我们所有的账户信息都会丢失。于是就有了Session,在一个session中保存了Session中的重要信息,浏览器只记录SessionId。一个SessionId对应一个会话请求。@RequestMapping("/testSession")@ResponseBodypublicStringtestSession(HttpSessionsession){session.setAttribute("testSession","thisismysession");返回"testSession";}@RequestMapping("/testGetSession")@ResponseBodypublicStringtestessession(HtsiontpSessionsession=).getAttribute("testSession");returnString.valueOf(testSession);}这里我们写了一个新的方法来测试Session是如何生成的,我们在请求参数中加入HttpSession会话,然后在浏览器中输入http://访问/localhost:8005/testSession,可以看到在服务器的返回头中的Cookie中生成了一个SessionId。那么浏览器就记住了这个SessionId,下次访问的时候可以带上这个Id,然后就可以根据这个Id找到服务器上保存的信息了。这时候我们访问路径http://localhost:8005/testGetSession,发现我们已经获取到了上面Session中存储的信息。那么Session什么时候过期呢?Client:同cookie过期,如果不设置,默认是关闭浏览器就没有了,即再次打开浏览器时,初始请求头中没有SessionId。server:服务器的过期时间是真的过期了,就是服务器上Session中保存的数据结构有多久不可用了,默认是30分钟。既然我们知道Session是在服务器端管理的,那么看到这里你可能会有几个疑问,Session是在哪里创建的呢?Session存储在什么数据结构中?接下来我们看一下Session是如何管理的。会话管理在容器中进行管理。什么是容器?Tomcat、Jetty等都是容器。下面我们以最常用的Tomcat为例,看看Tomcat是如何管理Session的。ManageBase中的createSession用于创建Session。@OverridepublicSessioncreateSession(StringsessionId){//先判断会话数是否达到最大值,最大会话数可以通过参数设置if((maxActiveSessions>=0)&&(getActiveSessions()>=maxActiveSessions)){拒绝会议++;thrownewTooManyActiveSessionsException(sm.getString("managerBase.createSession.ise"),maxActiveSessions);}//复用或者新建一个Session对象,注意是Tomcat中的StandardSession//是HttpSession的具体实现类,而HttpSession是Servlet规范中定义的接口Sessionsession=createEmptySession();//初始化新Session的值session.setNew(true);session.setValid(true);session.setCreationTime(System.currentTimeMillis());//设置Session过期时间为30分钟//这里将Session添加到ConcurrentHashMapsessionCounter++;//将创建时间添加到LinkedList中,并去掉第一次添加的时间//主要是为了清理过期的SessionSessionTimingtiming=newSessionTiming(session.getCreationTime(),0);synchronized(sessionCreationTiming){sessionCreati钛ming.add(timing);sessionCreationTiming.poll();}returnssession}至此我们明白了Session是如何创建的。Session创建后,Session会保存在一个ConcurrentHashMap中。您可以看到StandardSession类。protectedMapsessions=newConcurrentHashMap<>();至此,大家应该对Session有了一个简单的认识。>Session保存在Tomcat容器中,所以如果后端有多台机器,Session不能在多台机器之间共享。这时候可以使用Spring提供的分布式Session解决方案,就是将Session放在Redis中。TokenSession在服务器端存储了需要验证的信息,并使用SessionId来对应数据。SessionId由客户端保存,请求时也带上SessionId,从而实现状态对应。Token是将用户信息在服务端进行Base64Url编码后传递给客户端。每次用户请求都会带上这条信息,所以服务器收到这条信息解密后就知道用户是谁了。这种方法称为JWT(JsonWebToken)。>Token与Session相比的优势在于,当有多个后端系统时,由于客户端访问时直接携带数据,不需要共享数据。Token的优点很简单:可以通过URL、POST参数或者HTTP头参数来发送,因为数据量小,传输速度也快。它以Json的形式存储在客户端,所以JWT是跨语言的,不需要在服务端保存session信息。它特别适用于分布式微服务。实际的JWT结构大概如下,是一个很长的字符串,用.分成三部分。在中间。JWT由三部分组成。Header是一个描述JWT元数据的Json对象,通常如下{"alg":"HS256","typ":"JWT"}上面代码中,alg属性表示签名算法(algorithm),而默认是HMACSHA256(写成HS256);typ属性表示token(令牌)的类型,JWTtoken统一写为JWT。最后,将上述JSON对象通过Base64URL算法转换为字符串。>JWT作为令牌(token)使用,在某些场合可能会放在URL中(比如api.example.com/?token=xxx)。base64有+、/、=三个字符,在URL中有特殊含义,所以必须替换:=省略,+替换为-,/替换为_。这就是Base64URL算法。PayloadPayload部分也是一个Json对象,用来存放实际需要传输的数据。JWT官方规定了以下官方字段供选择。iss(issuer):发行者exp(expirationtime):过期时间sub(subject):主题aud(audience):受众nbf(NotBefore):生效时间iat(IssuedAt):发行时间jti(JWTID):序号当然,除了官方字段,我们也可以自己定义私有字段。下面是一个例子{"name":"xiaoMing","age":14}默认情况下,JWT是不加密的。Base64解码后可以读取信息,所以一般不要在这部分放机密信息。这个Json对象也应该使用Base64URL算法转换为字符串。Signature部分是对前面两部分的数据进行签名,防止数据被篡改。首先,你需要定义一个秘钥,这个秘钥只有服务器知道,不能泄露给用户。然后使用Header中指定的签名算法(默认为HMACSHA256),计算出签名后,将Header、Payload、Signature三部分组合成一个String,每部分之间用.隔开,返回即可用户。>HS256可以使用单个密钥为给定的数据样本创建签名。当传输带有签名的消息时,接收方可以使用相同的密钥来验证签名是否与消息匹配。AboveweintroducedsomeconceptsaboutJWT,howtouseitnext?FirstintroducetheJarpackagecompile('io.jsonwebtoken:jjwt:0.9.0')intotheprojectandthencodeasfollows//Signaturealgorithm,thetokenwillbesignedSignatureAlgorithmsignatureAlgorithm=SignatureAlgorithm.HS256;//SignJWTbyte[]apiKeySecretBytesbysecretkey=DatatypeConverter.parseBase64Binary("SECRET");KeysigningKey=newSecretKeySpec(apiKeySecretBytes,signatureAlgorithm.getJcaName());MapclaimsMap=newHashMap<>();claimsMap.put("name","xiaoMing");claimsMap.put("age",14);JwtBuilderbuilderWithSercet=Jwts.builder().setSubject("subject").setIssuer("issuer").addClaims(claimsMap).signWith(signatureAlgorithm,signingKey);System.out.printf(builderWithSercet.compact());发现输出的Token如下eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwibmFtZSI6InhpYW9NaW5nIiwiYWdlIjoxNH0.3KOWQ-oYvBSzslW5vgB1D-JpCwS-HkWGyWdXCP5l3Ko此时在网上随便找个Base64解码的网站就能将信息解码出来总结相信大家看到这应该对IhaveacertainunderstandingofCookie,Session,andToken.Next,Iwillreviewtheimportantknowledgepoints.Cookiesarestoredintheclient.Session是保存在服务器端的,可以理解为一个状态列表,有唯一的会话标识SessionId。可以在服务器端根据SessionId查询存储的信息。Session会导致一个问题,就是后端有多台机器时Session共享的问题。解决方案可以使用Spring提供的框架。Token类似于代币。它是无国籍的。服务器需要的信息经过Base64编码后放入Token中。服务器可以直接解码其中的数据。