最近在复习,所以又去学习了一遍这个,之前只是直接拿来用了,所以这次学习还是学到挺多。
目录
什么是JWT?
JSON Web Token (JWT) 是一个开放标准 (RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
上面是JWT的官方解释,官网地址JSON Web Tokens - jwt.io
而在我学习一些后通俗些讲,JWT(JSON Web Token)就是基于token认证的代表,是一种最常见的token生成的方式。
在此,就想来讨论一下基于token认证。而谈到了基于token认证,就不得不也提到在这之前的基于session 认证。
(本人是直接使用前者,所以对基于session认证了解甚少,正好学习一下)
1、基于session认证
HTTP是无状态的协议(对于事物处理没有记忆能力,每次客户端与服务端会话完成时,服务端不会保存任何会话信息) ,每次请求都是完全独立,服务端无法确认当前访问者的身份信息,也无法辨别上一次和这一次是否为同一对象。所以服务器与浏览器为了进行会话跟踪,就必须要去维护一个状态,这个状态会告诉服务端前后两个请求是否来自同一浏览器,这个状态就需要cookie和session去实现。
session是基于cookie实现的,cookie通过在客户端记录信息确定用户身份,session通过在服务器端记录信息确定用户身份。session存储在服务器端,sessionId会被存储到户端的cookie中。
session认证流程:
用户认证成功后,在服务端生成用户相关的数据保存在session中(当前会话),而发给客户端的sesssion_id 存放到 cookie 中,这样用客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验。当用户登出或过期时把服务端session销毁,客户端的session_id也就无效了。
而通过对于cookie和session的展开了解,会发些一些基于session的弊端。
基于Session认证所显露的问题:
- cookie是不可跨域的:每个cookie都会绑定单一的域名,无法在别的域名下使用,一级域名和二级域名之间是允许共享使用的(通过domain),由于基于cookie,所以session认证也无法跨域,对单点登录不适用。(单点登录(SSO):在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。)
-
服务器压力大:通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。
-
session共享:现在很多应用都是分布式集群,需要我们做额外的操作进行session共享;
-
CSRF跨站伪造请求攻击:session机制是基于浏览器端的cookie的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。并且如果浏览器禁用了cookie,这种方式会失效。
-
对于非浏览器的客户端,手机移动端等不适用,因为session依赖于cookie,而移动端经常没有cookie。
-
前后端分离系统中更加不适用,后端部署复杂,前端发送的请求往往经过多个中间件到达后端,cookie中关于session的信息会转发多次
2、基于Token认证
Token, 令牌,代表执行某些操作的权利的对象。
基于token认证流程:
- 客户端使用用户名和密码请求登录
- 服务端收到请求,去验证用户名和密码
- 验证成功后,服务端会签发一个token,再把这个token返回给客户端
- 客户端收到token后可以把它存储起来,比如放到cookie或者Local Storage中
- 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
- 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。localStorage理论上来说是永久有效的,即不主动清空的话就不会消失,即使保存的数据超出了浏览器所规定的大小,也不会把旧数据清空而只会报错。
这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下。
基于token认证的优点:
- 支持跨域访问:cookie无法跨域。将token置于请求头中可以解决这问题。
- 无状态化:服务端无需存储token,token自身包含了所有登录的用户信息,只需要验证token信息是否正确即可,这样一来可以减轻服务器压力。
- 避免CSRF跨站伪造攻击:由于不再依赖cookie,所以采用token认证方式不会发生CSRF。
- 更适用于移动端(Android,iOS,小程序等等):客户端是非浏览器平台时,cookie不被支持。
- 无需绑定到一个特殊的身份验证方案(传统的用户名密码登录),只需要生成的token是符合我们预期设定的即可;
-
更适用CDN:可以通过内容分发网络请求服务端的所有资料。
缺点:
- 相比较于传统的session登陆机制实现起来略微复杂一点
- 由于服务器不保存 token,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 token 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
而JWT就是上述的token的一种具体实现方式。
JWT介绍
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT Token,并且这个JWT Token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT的认证流程:
- 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个
POST
请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探 - 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个
JWT Token
,形成的JWT Token
就是一个如同lll.zzz.xxx
的字符串 - 后端将
JWT Token
字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token
即可 - 前端在每次请求时将
JWT Token
放入HTTP请求头中的Authorization
属性中(解决XSS和XSRF问题) - 后端检查前端传过来的
JWT Token
,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等 - 验证通过后,后端解析出
JWT Token
中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果
JWT使用
1、什么时候应该使用JWT?
-
授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
-
信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以您可以确定发件人就是他们所说的那个人。此外,由于使用标头和有效负载计算签名,您还可以验证内容没有被篡改。
2、JWT结构
将这三段信息文本用.
链接一起就构成了JWT字符串:
头部(header) + 载荷(payload) + 签证(signature)
因此,JWT通常为:xxxxx.yyyyy.zzzzz
1)header
JWT头部承载着两部分信息
-
声明类型,这里是JWT
-
声明加密的算法,通常直接使用 HMAC SHA256(写为HS256)
定义一个header:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后,这个 JSON 被Base64Url编码以形成 JWT 的第一部分。
2)payload
载荷就是存放有效信息的地方。
-
标准中注册的声明。这些是一组预定义的声明,它们不是强制性的,但建议使用,以提供一组有用的、可互操作的声明。
-
公共的声明。这些可以由使用 JWT 的人随意定义。但为避免冲突,它们应在IANA JSON Web 令牌注册表中定义或定义为包含抗冲突命名空间的 URI。
-
私有的声明。这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明。
标准中注册的声明(建议但不强制使用):
iss:jwt签发者
sub:jwt所面向的用户
aud:接收jwt的一方
exp:jwt的过期时间,这个过期时间必须要大于签发时间
nbf:定义在什么时间之前,该jwt都是不可用的
iat:jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而会比重放攻击
公共的声明:
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密
私有的声明:
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
定义一个payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对有效负载进行Base64Url编码以形成 JSON Web 令牌的第二部分。
对于已签名的令牌,此信息虽然受到保护以防篡改,但任何人都可以读取。除非已加密,否则请勿将机密信息放入 JWT 的有效负载或标头元素中。JWT只是适合在网络中传输一些非敏感的信息
3)signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名
例如,使用 HMAC SHA256 算法,签名将通过以下方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用.
分隔,就构成整个JWT对象
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,他就是你服务端的私钥,在任何场景都不应该泄露出去,一旦客户得知这个secret,那就意味着客户端是可以自我签发jwt了。
3、JWT的使用
该如何应用到java中已经网上已经有很多示例了,在这我就不多搬运了。
为什么要使用JWT(JWT的优点)
-
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
-
因为有了playload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
-
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
-
它不需要在服务端保存会话信息,所以他易于应用的扩展。
-
简洁:
JWT Token
数据量小,传输速度也很快 -
单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
使用注意事项(安全)
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 由于缺乏安全性,也不应该在浏览器存储中存储敏感的会话数据。
-
不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
- secret就是你服务端的私钥,在任何场景都不应该泄露出去,一旦客户得知这个secret,那就意味着客户端是可以自我签发jwt了。
-
每当用户想要访问受保护的路由或资源时,用户代理应该发送 JWT,通常在Authorization标头中使用Bearer模式。标头的内容应如下所示:
Authorization: Bearer <token
如果令牌在Authorization
标头中发送,则跨域资源共享 (CORS) 不会成为问题,因为它不使用 cookie。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。(或者是对JWT在前后端之间进行加密之后在传输)。
- JWT的哈希签名的密钥是存放在服务端的,所以只要服务器不被攻破,理论上JWT是安全的。因此要保证服务器的安全
- JWT可以使用暴力穷举来破解,所以为了应对这种破解方式,可以定期更换服务端的哈希签名密钥(相当于盐值)。这样可以保证等破解结果出来了,你的密钥也已经换了