HTTP无状态问题
Web应用程序是基于BS((Browser/Server))架构,是通过浏览器访问服务器来提供各种功能和服务。浏览器和服务器之间的通讯将通过http协议进行传输和交换数据。而http协议是一个无状态的协议,协议本身不关注请求端是谁或者都有谁访问了,所以我们需要一套用户认证系统来识别用户和验证权限。
怎么理解这个无状态协议呢?
关于无状态,下面有两点解释:
1)首先,浏览器向服务器之间的每次请求链接都是短链接,并且每次连接只处理一个请求,而且处理完一个请求后就会立马断开,然后去处理队列中的下一个请求。
2) 其次,服务器是健忘的,对它来说,每次服务的客户都是新的客户,他记不住请求过的客户端,和处理过的事。所以服务器是不会认识请求的客户端,它只负责接收请求并给出响应,不管你是否请求过,也不管你是谁,要什么给什么。别想着搞关系,一视同仁。
但是现实情况是,我们需要根据不同的用户提供各种不同的服务,需要知道用户是否登陆,需要对用户进行分类,需要知道用户是否有权限使用各种功能等等,如果按照http协议通讯,后台服务器无法从访问的连接中认出请求的人是谁,那该怎么办?
为此,我们的服务器需要有一套认证系统,通过浏览器请求中传来的数据(相关账号信息)来判断用户信息。
你可能会说,那还不简单,浏览器只要在每次访问是都带上用户名和密码,那不就可以了吗?
原理上,确实是可以。但是这样服务器不是每次都要先访问数据库进行验证用户真实性,那服务器不累趴下了;另外,这样是很危险的,用户的用户名和密码很容易被盗取。因为如果要每次访问都带上,那么浏览器必须保存用户名和密码信息,但是浏览器保存的东西是很危险的!
安全、高效的解决方案是,在用户登陆后,服务器给用户一把钥匙,用户每次访问是都带上钥匙;服务器收到钥匙后进行验证钥匙的有效性,这样就避免了用户传输用户名和密码等敏感信息,也提高了服务器的效率,不用每次都和数据库交互验证。
方案一,传统的方法session认证
流程如下:
1) 在登陆页面,用户向服务器发送用户名和密码。
2) 服务器验证通过后,保存相关数据(用户名、角色、登录有效期、登陆状态等),即通常说的session;然后服务器把对应的session_id通过Cookie返回给客户端(Cookie的名字通常是session_id)。
3) 后面用户的每次请求都会自动带上这个Cookie,发送回服务器。
4) 最后服务器根据这个session_id找到事先保存好的数据信息来确认访问的用户身份。
这样我们就可以识别不同的用户,提供不同的服务。这个方案的优点是:
a) 服务器只需要验证一次用户名和密码,减少和数据库的交互次数;
b) 用户信息(session)一般存储在缓存或者缓存数据库中,获取和验证数据更快;
c) 客户端(浏览器)只需要存储session_id,没有任何敏感数据;
但是也有一些缺点:
a) 由于session存储在服务器的内存,当用户多时内存开销会比较大。
b) 扩展性差。如果单服务器很方便,但是多服务器时就要在服务器间共享session。
c) CSRF问题,由于客户端和服务器之间的session_id是通过cookie来传输的,容易被截获。
d) 有些客户端传输cookie不方便,特别是跨域时。
方案二,JWT(JSON WEB TOKENS)
1) 在登陆页面,用户向服务器发送用户名和密码。
2) 服务器验证通过后,返回一个令牌(Token)给客户端;这个Token存有编码后的用户数据,此用户数据没有加密,但是token中有签名,可供服务器验证。
3) 客户端收到后把令牌保存起来,等下次访问时带上传给服务器。
4) 服务器收到Token后,验证JWT的有效性,有效则继续访问。
这个方案比起第一个的优点是:
a) Token和http协议一样也是无状态的,在服务器端不需要缓存用户信息(session),减少缓存的使用,更不用在多服务器之间共享用户session登陆信息,简单、高效;
b) 可以很方便实现单点登陆功能(SSO)。
这个方案可以更高效的解决认证问题,可以省去很多资源和繁琐的处理逻辑。但是要注意的是,如果这个token被截取了就会有被盗用身份的风险。我们需要根据对风险的接受程度来调整token的有效期,有效期越短,风险就越低;只要在很短是有效期内,就算被盗用了,风险也不会很大。
关于JWT
什么是JWT?
JSON Web Token (JWT),是一个开发的标准(RFC 7519),定义了网络端之间如何安全地传输信息的方式。协议是基于JSON的,传输的信息(Token)是一个JSON Object。因为这个Token是有数字签名的,所以传输的信息是可验证的,安全可靠。JWT可以是单密钥使用HMAC算法签名,也可以是非对称的双密钥使用RSA或ECDSA签名。
JWT的结构
JWT由三部分组成,分别是:
1) Header (头部)
2) Payload (有效载荷)
3) Signature (签名)
这三部分经过Base64编码后按顺序用 “.” 连接起来便是一个jwt,如下:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded Signature>
Header
header是一个JSON,通常包括两个部分:token的类型(jwt)和签名用到的算法如HMAC SHA256 或者 RSA。具体如下:
{
"alg":"HS256",
"typ": "JWT"
}
这个JSON在经过Base64Url编码后就是Header了。
Payload
Payload,即JWT的主要声明信息,包含了需要传输的用户信息及实际应用中的额外数据,也是一个JSON对象,对象中的每一个字段就是一个声明。JWT协议中规定了7个标准的注册字段,如下:
1) iss (Issuer):JWT签发人
2) sub (Subject):JWT主题
3) aud (Audience):JWT受众
4) exp (Expiration Time):JWT过期时间
5) nbf (Not Before):JWT生效时间
6) iat (Issued At):JWT签发时间
7) jti (JWT ID):JWT的唯一身份标识
除了这几个标准的字段,还可以添加自己需要的字段。JSON的样子如下:
{
"unique_name": "张三",
"exp": 1625036429,
"iat": 1625034629
}
这个JSON同样经过Base64Url编码后就是第二部分Payload。一般JWT 默认是不加密的,任何人都可以读到,所以不要把敏感信息放进去。
Signature
这个部分是由前面两个部分的加密字符串。生成过程如下:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
三部分合计来就是一个JWT,如下图:

最后一个JWT长这个样子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IuW8oOS4iSIsIm5iZiI6MTYyNTAzNDYyOSwiZXhwIjoxNjI1MDM2NDI5LCJpYXQiOjE2MjUwMzQ2Mjl9.rqwOXVmP_WuacQMUGRlRa0X9AowWQ5FAsLP6OAa94fo
我们可以去网站jwt.io将其转换成原始数据,更清晰的感受一下各个部分的结构。

JWT的应用
一般客户端收到token后,每次发送请求的时候在请求头(Header)里加入Authorization,并加上Bearer标注(注意Bearer后面要有个空格):
Authorization: Bearer <token>

后台服务器收到token后,根据之前生成token的密钥(secret)解密,获取到的信息对比token的签名两个部分进行验证,验证通过则token通过,服务器将允许访问。
参考文档: