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

Cookie、Session、Token不懂?这篇文章会让你对

时间:2023-03-12 05:26:59 科技观察

有一个完整的认识。很多朋友还不清楚Cookie、Session、Token的区别。今天就带大家彻底了解它们。图片来自PexelsCookie夏洛:大叔,马冬梅家住322楼上吧?叔叔:什么是马?夏洛特:马冬梅。叔叔:没什么?夏洛特:马冬梅。叔叔:马怎么了?夏洛特:好的,先生,您应该先冷静下来。在了解这三个概念之前,我们需要了解HTTP是一种无状态的Web服务器。什么是无国籍?就像上面夏洛特烦恼中的经典对话一样,一个对话完成后,接下来的对话就完全未知了。谈话发生了什么。如果只是用来管理web服务器中的静态文件,那么不管对方是谁,直接从磁盘中读取文件发送出去即可。但是随着网络的不断发展,比如电子商务中的购物车,只有记住了用户的身份,才能进行接下来一系列的动作。所以在这一点上我们需要我们的无状态服务器记住一些东西。那么网络服务器是如何记住一些东西的呢?既然web服务器记不住东西,那我们就尽量在外部记住,相当于服务器给每个client贴一张小纸条。上面记录了服务器返回给我们的一些信息。然后服务器看到这个小纸条就知道我们是谁了。那么是谁生成了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中的值记住我们的信息了:接下来,要不要再发起一次请求?曲奇也会带过来吗?接下来我们输入路径http://localhost:8005进行请求。我们可以看到cookie字段还是被接管了:浏览器cookie存放在哪里?如果您使用的是Chrome浏览器,那么您可以按照以下步骤操作:在电脑上打开Chrome,右上角,点击更多图标→底部的设置,点击高级,在隐私设置和安全下,点击网站设置,点击Cookies→查看所有Cookies和网站数据,然后根据域名搜索托管的Cookies数据。因此浏览器会为您管理cookie数据。如果此时切换到其他浏览器如Firefox,由于刚才在Chrome中保存了cookie,服务器又被包围了。如果你不知道你是谁,你会再次在Firefox上放一个小纸条。cookies中的参数设置说到这里大家应该知道cookies是服务器委托浏览器存储在客户端的一些数据,这些数据通常记录了用户的关键身份信息。因此,cookie需要通过一些其他方式进行保护,以防止泄露或被盗。这些手段就是cookies的属性。下面我简单演示一下这些参数的用法和现象:①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的字段。这是由Domain控制的域发送cookie的内容。下面几个参数就不一一演示了。相信这里大家应该对cookies有所了解。SessionCookie保存在客户端,Session保存在服务器端,客户端只保存SessionId。在上面,我们了解了什么是cookie。既然浏览器已经通过cookie实现了有状态的需求,为什么还要多一个Session呢?这里我们想象一下,如果cookie中保存了账户的一些信息,一旦信息被拦截,我们所有的账户信息都会丢失。于是Session就出现了,一个session中重要的信息就保存在Session中。浏览器只记录SessionId,一个SessionId对应一个session请求。@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什么时候过期呢?客户端:和Cookie过期一样。如果不设置,默认是关闭浏览器,就没有了。即再次打开浏览器时,初始请求头中没有SessionId。Server:服务器的过期时间,真正过期,即服务器上Session中保存的数据结构多长时间不可用,默认30分钟。既然我们知道Session是在服务器端管理的,看到这里你可能会有几个疑问。Session是在哪里创建的?Session存储在什么数据结构中?接下来,让我们看看Session是如何工作的。管理。会话管理在容器中进行管理。什么是容器?Tomcat、Jetty等都是容器。下面我们以最常用的Tomcat为例,看看Tomcat是如何管理Session的。ManageBase中的createSession用于创建Session:@OverridepublicSessioncreateSession(StringsessionId){//首先判断Session个数是否达到最大值,最大Session个数可以通过参数if((maxActiveSessions>=0)&&设置(getActiveSessions()>=maxActiveSessions)){rejectedSessions++;thrownewTooManyActiveSessionsException(sm.getString("managerBase.createSession.ise"),maxActiveSessions);}//重用或者创建一个新的Session对象,注意是StandardSession//中Tomcat是HttpSession的具体实现类,HttpSession是Servlet规范中定义的接口Sessionsession=createEmptySession();//初始化新Session的值session.setNew(true);session.setValid(true);session.setCreationTime(System.currentTimeMillis());//设置session过期时间为30分钟session.setMaxInactiveInterval(getContext().getSessionTimeout()*60);Stringid=sessionId;if(id==null){id=generateSessionId();}会话。setId(id);//这里会把Session添加到ConcurrentHashMap中sessionCounter++;//将创建时间添加到LinkedList中,并去掉第一次添加的时间//主要是为了清理过期的SessionSessionTimingtiming=newSessionTiming(session.getCreationTime(),0;同步ized(sessionCreationTiming){sessionCreationTiming.add(timing);sessionCreationTiming.poll();}returnssession}至此我们了解了Session是如何创建的。Session创建后,Session会保存在一个ConcurrentHashMap中。可以看到StandardSession类:protectedMapwww.jintianxuesha.comsessions=newConcurrentHashMap<>();至此,大家应该对Session有了一个简单的认识。Session是保存在Tomcat容器中的,所以如果后端有多台机器,Session是不能在多台机器之间共享的。这时候可以使用Spring提供的分布式Session解决方案,就是将Session放到Redis中。TokenSession在服务器端存储了需要验证的信息,并使用SessionId来对应数据。SessionId由客户端保存,请求时也带上SessionId,从而实现状态对应。Token是将用户信息在服务端进行Base64Url编码后传递给客户端。每次用户请求都会带上这条信息,所以服务器收到这条信息解密后就知道用户是谁了。该方法称为JWT(JsonWebToken)。Token与Session相比的优势在于,当有多个后端系统时,由于客户端访问时直接携带数据,不需要共享数据。Token的优点:简单:可以通过URL、POST参数或HTTP头参数发送,因为数据量小,传输速度快。Self-contained:由于字符串中包含了用户需要的信息,避免了对数据库的多次查询。因为Token是以Json的形式存储在客户端的,所以JWT是跨语言的。不需要在服务器端保存session信息,尤其是分布式微服务。JWT的结构实际的JWT看起来像这样。它是一个很长的字符串,由.分成三部分。在中间。JWT由三部分组成:①HeaderHeader是一个描述JWT元数据的Json对象,通常如下:{"alg":"HS256","typ":"JWT"}上面代码中:alg属性表示签名algorithm(算法),默认是HMACSHA256(写成HS256)。type属性表示令牌(Token)的类型,JWT令牌统一写为JWT。最后将上面的Json对象通过Base64URL算法转换为字符串。JWT作为令牌(Token)使用,在某些场合可能会放在一个URL中(比如api.example.com/?token=xxx)。base64有+、/、=三个字符,在URL中有特殊含义,所以必须替换:=省略,+替换为-,/替换为_。这就是Base64URL算法。②PayloadPayload部分也是一个Json对象,用来存放实际需要传输的数据。JWT官方规定了以下几个官方字段供选择:iss(issuer):issuerexp(expirationtime):过期时间sub(subject):subjectaud(audience):Audiencenbf(NotBefore):生效时间发布时间jti(JWTID):Number当然,除了官方字段,我们也可以自己定义私有字段。下面是一个例子:{"name":"xiaoMing","age":14}默认情况下,JWT是不加密的。网上任何人都可以通过解码Base64来读取信息,所以一般不要在这部分放机密信息。这个Json对象也应该使用Base64URL算法转换为字符串。③签名签名部分是对前两部分的数据进行签名,防止数据被篡改。首先需要定义一个秘钥,只有服务器知道,不能向用户泄露,然后使用Header中指定的签名算法(默认为HMACSHA256)。签名计算完成后,将Header、Payload、Signature三部分组合成一个字符串,每部分之间用.隔开,返回给用户。HS256可以使用单个密钥为给定的数据样本创建签名。当传输带有签名的消息时,接收方可以使用相同的密钥来验证签名是否与消息匹配。Java中如何使用Token上面我们介绍了JWT的一些概念,接下来怎么使用呢?首先引入项目中的Jar包:compile('io.jsonwebtoken:jjwt:0.9.0')代码如下://signatureAlgorithm,对token进行签名SignatureAlgorithmsignatureAlgorithm=SignatureAlgorithm.HS256;//secret签名键JWTbyte[]apiKeySecretBytes=DatatypeConverter.parseBase64Binary("SECRET");KeysigningKey=newSecretKeySpec(apiKeySecretBytes,signatureAlgorithm.getJcaName());Object>claimsMap=newHashMap<>();claimsMap.put("姓名","小明");claimsMap.put("年龄",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此时JustfindaBase64decodingwebsiteontheInternettodecodetheinformation:IbelievethateveryoneshouldhaveacertainunderstandingofCookie,Session,andTokenafterseeingthis.接下来回顾一下重要知识点:Cookies是保存在客户端的。Session是保存在服务器端的,可以理解为一个状态列表。具有唯一的会话IDSessionId。可以在服务器端根据SessionId查询存储的信息。Session会导致一个问题,就是后端有多台机器时Session共享的问题。解决方案可以使用Spring提供的框架。Token就像令牌一样,无状态,服务器需要的信息经过Base64编码后放入Token中,服务器可以直接对其中的数据进行解码。GitHub代码地址:https://github.com/modouxiansheng/Doraemon