JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。它为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包含JWT,允许用户访问该令牌允许的路由、服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域名中轻松使用(跨域)。
信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以被签名,例如,使用公钥/私钥对你可以确定发件人的真实性。此外,由于使用header 和 payload 计算签名,你还可以验证内容是否未被篡改。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。jwt的头部承载两部分信息:
➤声明类型,这里是jwt
➤声明加密的算法 通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
(1)标准中注册的声明
标准注册声明不是强制使用是的,但是我建议使用。它一般包括以下内容:
●iss:jwt的签发者/发行人;
●sub:主题;
●aud:接收方;
●exp:jwt过期时间;
●nbf:jwt生效时间;
●iat:签发时间
●jti:jwt唯一身份标识,可以避免重放攻击
(2)公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
(3)私有的声明
私有声明是服务器和客户端共同定义的声明,同样这里不建议添加敏感信息。
下面这个代码段就是定义了一个有效载荷:
json{“exp”:“201909181230”,“role”:“admin”,“isShow”:false}
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
●header (base64后的)
●payload (base64后的)
●secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
1)新建项目中的pom.xml中添加依赖:
io.jsonwebtoken
jwt
0.9.0
(2)创建个demo类,如下
JwtBuilder builder= Jwts.builder()
.setId("666") //设置一个唯一编号
.setSubject("小凝")//设置一个主题,这里也可以是一个
.setIssuedAt(new Date())//设置一个签发日期
.signWith(SignatureAlgorithm.HS256,"lalalalaa");//设置签名这里我们使用HS256算法,并设置加盐SecretKey(字符串)
//输出一下
System.out.println( builder.compact() );
运行打印结果:
urjfkfjfJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJUHihdsfjkopkfhbpo[lkpodfkTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXXreFU_u3O
再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。
我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiIiOiLlsI_nmb0iLCJpYF9.ThecMfgYjtoys3JX7dXXreFU_u3Y";
Claims claims = Jwts.parser().setSigningKey("hahaha").parseClaimsJws(compactJwt).getBody();
//打印结果
System.out.println(claims);
运行打印效果:
{jti=666, sub=小凝, iat=1557904181}
如果将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token.
有很多时候并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。
(1)创建token 并设置过期时间
long now=System.currentTimeMillis();
long exp=now+1000*30;//设置30秒过期
JwtBuilder jwtBuilder = Jwts.builder().setId( "666" )//设置一个唯一编号
.setSubject( "小凝" )//设置主题
.setIssuedAt( new Date() )//签发的时间
.setExpiration( new Date( exp ) )//过期的时间
.signWith( SignatureAlgorithm.HS256, "lalalala" );
String token = jwtBuilder.compact();
System.out.println(token);
运行,打印效果如下:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiIiOiLlsI_nmb0iLCJpYXQiUzMmV4NTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI
(2)解析TOKEN
String compactJwt="eyJhbGI1NiJ9.eyJqdiLlsI_nmb0iLCJpYXQ4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI";
Claims claims = Jwts.parser().setSigningKey("hahaha").parseClaimsJws(compactJwt).getBody();
System.out.println(claims);
当前时间超过过期时间,则会报错。
在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,你不应该让令牌保留的时间过久。
每当用户想要访问受保护的路由或资源时,用户代理通常应该使用Authorization头发送JWT。如下所示:
Authorization: Bearer
这在一些场景中可以实现无状态授权机制。服务器的受保护路由将检查Authorization标头中的有效JWT ,如果存在,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库以进行某些操作的需要。
如果在Authorization头中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。
下图显示了如何获取JWT并用于访问API或资源:
1.应用程序或客户端向授权服务器请求授权。
2.授予授权后,授权服务器会向应用程序返回访问令牌。
3.应用程序使用访问令牌来访问受保护资源(如API)。
请注意,使用签名令牌,令牌中包含的所有信息都会向用户或其他方公开,即使他们无法更改。这意味着你不应该在令牌中放置秘密信息。
●因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
●因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
●便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
●它不需要在服务端保存会话信息, 所以它易于应用的扩展
●不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
●保护好secret私钥,该私钥非常重要。
●如果可以,请使用https协议