要解决的问题:跨域认证问题
互联网服务的用户认证流程:
- 用户向服务器发送用户名和密码;
- 服务器验证通过后,在当前会话session里面保存相关数据,比如用户角色,登陆时间等;
- 服务器向用户返回一个session_id,写入用户的Cookie;
- 用户随后的每一次请求都会携带Cookie,将session_id传回服务器。
- 服务器收到session_id后,根据他找到之前保存的该用户的信息,得知用户身份。
缺点
- 扩展性不好。单机当然没有问题,如果是服务器集群,或是跨域的服务导向架构,就要求session数据共享,要求每台服务器都能够读取到session;
- 占用服务器资源。需要服务器来保存用户的信息,如果在千万级数据量下,会很耗服务器资源;
解决方案
- session数据持久化,将session写入数据库或别的持久层,各种服务收到请求后,都向持久层请求数据,虽然这种方案架构清晰,但是工程量大,另外,如果持久层万一挂了,就会单点失败。
- 服务器不保存session数据了,所有数据都保存在客户端,每次请求都发回服务器,JWT就是这种方案的一个代表。
了解JWT
原理
服务器认证以后,生成一个JSON对象,发回给用户:
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
以后,用户与服务端通信的时候,都要发回这个JSON对象,服务器完全只靠这个对象认定用户身份。为了发哪个会用户篡改数据,服务器在生成这个对象的时候,会加上签名。
服务器就不用保存任何session数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
什么是服务器有无状态?
有状态服务:服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求处理,典型的设计如tomcat的session;
缺点:
- 服务端保存大量数据,增加服务端压力;
- 服务端保存用户状态,无法进行水平扩展;
- 客户端请求依赖服务端,多次请求必须访问同一台服务器;
有状态服务:微服务集群中的每个服务,对外提供的都是Rest风格的接口,而Rest风格的一个最重要的规范就是服务的无状态性,即服务端不保存任何客户端请求者信息,客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份。
优点:
- 客户端请求不依赖服务端信息,任何多次请求不需要必须访问到同一台服务;
- 服务端请求集群和状态对客户端透明,即对客户端来将集群相当于只有一台服务器;
- 服务端可以任意的迁移和伸缩;
- 减小服务端存储压力;
JWT的数据结构
实际上的JWT大概是这样的:
它是一个很长的字符串,中间用点.
分割成三个部分,JWT内部是没有换行的;
三个部分依次如下:
- Header(头部)
- Payload(负载)
- Signature(签名)
Header
Header部分是一个JSON对象,描述JWT的元数据
{
"alg": "HS256",
"typ": "JWT"
}
其中,alg
代表签名的算法,默认是HMAC SHA256(HS256);typ
属性表示这个令牌(token)的类型;最后将这个JSON对象使用Base64URL算法转成字符串;
Payload
Payload部分也是一个JSON对象,用来存放实际需要传递的数据
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
还可以在这个部分定义私有字段
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意:JWT默认是不加密的,任何人都能读到,所有不要把私密信息放在这个部分。这个JSON对象也要使用Base64URL算法转成字符串。
Signature
Signature部分是对前两部分的签名,防止数据篡改。
首先,需要制定一个密钥(secret),这个密钥只有服务器知道,不能泄露给用户,然后使用Header里面指定的签名算法,按照下面的公式产生签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名后,把Header、Payload、Signature三部分拼成一个字符串,每个部分用.
分割,就可以返回给客户。
签名的目的
如果有人对头部以及载荷的内容解码之后在进行修改,然后再进行编码,那么新的头部和载荷计算出来的签名和这个JWT内的签名就是不一致的。 而对jwt的签名如果不知道加密的密钥,是不能修改的。
服务器应用在接受到JWT之后,会首先对头部和载荷的内容用同一种算法再次签名,如果服务器应用对头部和载荷再次以同样算法签名之后发现,自己计算出来的签名和接收到的签名不一样,那就说明这个Token的内容被别人动过,我们应该拒绝这个Token,返回一个HTTP401 Unauthorized响应。
JWT交互流程
- 用户登录;
- 服务的认证,通过后根据secret生成token;
- 将生成的token返回给浏览器;
- 用户每次请求携带token
- 服务器端解读jwt签名,判断有效后,从Payload中获取用户信息;
- 处理请求,返回响应结果。
因为JWT签发的token中已经包含了用户身份信息,并且每次请求都会携带,这样服务端就无需保存用户信息,甚至无需在取数据库查询,完全符合Rest的无状态规范。
JWT的几个特点
- JWT默认是不加密的,但是也可已加密,生成原始Token之后,可以用密钥再加密一次;
- JWT不加密的情况下,不能将秘密数据写入JWT;
- JWT不仅可以用于认证,也可以用于交换信息,有效使用JWT,可以降低服务器查询数据库的次数;
- JWT的最大缺点是,由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限。也就是说,一旦JWT签发了,在到期之前就会始终有效,除非服务器部署额外逻辑;
- JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置的比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议传输