Cookie Session Token JWT
Cookie的出现
HTTP协议是无状态的,但是服务器上运行的业务要有状态。
Cookie的最初目的就是为了存储状态信息,以方便服务器端使用。
比如:如果没有状态,我们就无法在服务端判断某个用户是否是第一次访问该网站
Cookie
Cookie是客户端技术,程序把每个用户的数据以cookie的形式返回给用户的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的cookie数据。这样,web资源处理的时候就可以区分用户,对应的就是不同用户的数据。cookie处理:
- 服务器用
SetCookie向客户端发送cookie - 浏览器保存
cookie - 之后每次
http请求浏览器都会将cookie带给服务器端
Java提供的操作Cookie的API
Java中的类javax.servlet.http.Cookie用于创建一个Cookie
| 方法 | 类型 | 描述 |
|---|---|---|
Cookie(String name,String value) |
构造方法 | 实例化Cookie对象, 传入cooke名称和cookie的值 |
public String getName() |
普通方法 | 取得Cookie的名字 |
public String getValue() |
普通方法 | 取得Cookie的值 |
public void setValue(String newvalue) |
普通方法 | 设置Cookie的值 |
public void setMaxAge(int expiry) |
普通方法 | 设置Cookie的最大保存时间, 即cookie的有效期。 |
public int getMaxAge() |
普通方法 | 获取Cookie的有效期 |
public void setPath(String Url) |
普通方法 | 设置cookie的有效路径 |
public String getPath() |
普通方法 | 获取cookie的有效路径 |
public void setDomain(String pattern) |
普通方法 | 设置cookie的有效域 |
public String getDomain() |
普通方法 | 获取cookie的有效域 |
1 | public void setMaxAge(int expiry) |
response接口中定义了一个addCookie方法,用于在响应头中增加一个Set-Cookie头字段;request接口中定义了一个getCookies方法,用于获取客户端提交的Cookie。
1 | package gac.xdp.cookie; |
Session
- session的处理:
- 浏览器第一次访问服务器,服务器会创建一个session,并生成一个sessionId
- 将sessionid及对应的session分别作为key和value保存到缓存中,也可以持久化到数据库中
- 服务器再把sessionid,以cookie的形式发送给客户端
- 浏览器下次再访问时,会直接带着cookie中的sessionid。然后服务器根据sessionid找到对应的session进行匹配;
- session常用方法
1 | public void setAttribute(String name,String value) |
- 基于session的用户认证


Token(访问资源的令牌)
token处理流程
- 浏览器把用户的用户名和密码发到后端
- 后端进行校验,校验成功会生成token, 把token发送给客户端
- 客户端自己保存token, 再次请求要在http请求头中带着token去访问服务端,和服务端保存的token信息进行比对校验。

JWT(json web token)
组成
一个jwt实际上就是一个字符串,它由三部分组成,头部、载荷与签名,这三个部分都是json格式。
一、头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。
1 | { |
在这里,我们说明了这是一个JWT,并且我们所用的签名算法是HS256算法。
二、载荷(Payload)
1 | { |
验证流程
- 在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串
- 在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串
- 使用在header中声明的加密算法和每个项目随机生成的secret来进行加密, 把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。
- 解密的时候,只要客户端带着JWT来发起请求,服务端就直接使用secret进行解密。
这里面的前五个字段都是由JWT的标准所定义的。
- iss: 该JWT的签发者
- sub: 该JWT所面向的用户
- aud: 接收该JWT的一方
- exp(expires): 什么时候过期,这里是一个Unix时间戳
- iat(issued at): 在什么时候签发的
把头部和载荷分别进行Base64编码之后得到两个字符串,然后再将这两个编码后的字符串用英文句号.连接在一起(头部在前),形成新的字符串:
1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0 |
三、签名(signature)
最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。加密后的内容也是一个字符串,最后这个字符串就是签名,把这个签名拼接在刚才的字符串后面就能得到完整的jwt。header部分和payload部分如果被篡改,由于篡改者不知道密钥是什么,也无法生成新的signature部分,服务端也就无法通过,在jwt中,消息体是透明的,使用签名可以保证消息不被篡改。
特点:
- 三部分组成,每一部分都进行字符串的转化
- 解密的时候没有使用数据库,仅仅使用的是secret进行解密。
1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM |
JWT和session的区别
基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。
- 应用程序分布式部署的情况下,session需要做多机数据共享,通常可以存在数据库或者redis里面。而jwt不需要。
- jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。
jwt的缺点
- 安全性
由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。 - 性能
jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage里面。并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。 - 一次性
无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。
(1)无法废弃
通过上面jwt的验证机制可以看出来,一旦签发一个jwt,在到期之前就会始终有效,无法中途废弃。例如你在payload中存储了一些信息,当信息需要更新时,则重新签发一个jwt,但是由于旧的jwt还没过期,拿着这个旧的jwt依旧可以登录,那登录后服务端从jwt中拿到的信息就是过时的。为了解决这个问题,我们就需要在服务端部署额外的逻辑,例如设置一个黑名单,一旦签发了新的jwt,那么旧的就加入黑名单(比如存到redis里面),避免被再次使用。
(2)续签
如果你使用jwt做会话管理,传统的cookie续签方案一般都是框架自带的,session有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。一样的道理,要改变jwt的有效时间,就要签发新的jwt。最简单的一种方式是每次请求刷新jwt,即每个http请求都返回一个新的jwt。这个方法不仅暴力不优雅,而且每次请求都要做jwt的加密解密,会带来性能问题。另一种方法是在redis中单独为每个jwt设置过期时间,每次访问时刷新jwt的过期时间。
可以看出想要破解jwt一次性的特性,就需要在服务端存储jwt的状态。但是引入 redis 之后,就把无状态的jwt硬生生变成了有状态了,违背了jwt的初衷。而且这个方案和session都差不多了。
基于token的认证方案
