JWT的初步了解

1. 什么是JWT

我们首先访问JWT的官方网站(JSON Web Token Introduction - jwt.io),可以看到如下简介:

翻译成中文:

Json Web Token(JWT) 是一个开放标准(RFC 7519),它定义了一种紧凑的,自包含的方式,用于在各方之间以json对象安全地传输信息,此信息可以验证和信任.因为它是数字签名的,jwt可以使用加密算法(使用HMAC 算法) 或使用RSA或ECDSA 的公钥/私钥进行签名.

通俗的讲: JWT就是通过json格式作为Web应用程序中的令牌,用于在各方之间安全地将信息以JSON的形式传输,在数据传输过程中.还可以对数据进行加密,签名等相关处理.

2.JWT的使用场景

以下是JSON Web 令牌的使用场景:

  • 授权: 这是使用JWT最常见方式,用户登录后,每个后续请求都将包含JWT,从而允许用户访问该令牌允许的路由,服务和资源.单点登录是当今广泛使用JWT的一项功能,因为它的开销很少并且可以在不同的域中轻松使用.

  • 信息交换: JSON Web 令牌是在各方之间安全地传输信息的一种好方式.因为可以对JWT进行签名(比如:使用公钥或私钥对),所以你可以确保发件人就是本人,此外,由于签名是使用标头和有效负载计算的,因此你还可以验证内容是否被篡改.

3.JWT与Session的差异:

相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。

Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。

Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。

JWT的优势:

  • 简洁: 可以通过URL,POST参数或者Http 请求头发送,因为数据量小,传输速度也快

  • 自包含:负载中包含了用户所需要的数据,避免多次查询数据库

  • 因为Token是以JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上任何语言的Web项目都支持

  • 不需要再服务端保存会话信息,特别适用于分布式微服务.

4. JWT的结构

JSON Web令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔,分别是:

  • 头部(header)

  • 载荷 (payload)

  • 签名(signature)

JWT通常如下所示: xxxxxx.yyyyyy.zzzzz

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U

4.1 头部

头部通常由两部分组成:令牌的类型(即JWT)和正在使用的签名算法(如:HMAC SHA256 或RSA)

然后用Base64Url编码(该加密是可以对称解密的)得到头部: 即:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

4.2 载荷

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,载荷中放置了 token 的一些基本信息,以帮助接受它的服务器来理解这个 token。同时还可以包含一些自定义的信息,用户信息交换。这些有效信息包含三个部分

  • 标准中注册声明

  • 公共的声明

  • 私有的声明

标准中注册声明(建议不强制使用)

{
  "sub": "1",
  "iss": "http://localhost:8000/auth/login",
  "iat": 1451888119,
  "exp": 1454516119,
  "nbf": 1451888119,
  "jti": "37c107e4609ddbcc9c096ea5ee76c667",
  "aud": "dev"
}

这里面的7个字段都是由官方所定义的,也就是预定义(Registered claims)的,并不都是必需的。

  • iss (issuer):签发人

  • sub (subject):主题

  • aud (audience):接收方

  • exp (expiration time):过期时间

  • nbf (Not Before):生效时间,在此之前是无效的

  • iat (Issued At):签发时间

  • jti (JWT ID):编号

公共的声明

在使用 JWT 时可以额外定义的载荷。为了避免冲突,应该使用 IANA JSON Web Token Registry 中定义好的,或者给额外载荷加上类似命名空间的唯一标识。

公共的声明可以添加任何的信息,一般添加用户的相关信息或其它业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密;

私有的声明

在信息交互的双方之间约定好的,既不是预定义载荷也不是公有载荷的一类载荷。这一类载荷可能会发生冲突,所以应该谨慎使用。

比如我们定义了一个payload:

{ "sub": "1234567890", "name": "John Doe", "admin": true }

然后将其base64url加密,得到jwt的一部分

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

请注意,对于已签名的令牌,此信息尽管可以防止篡改,但任何人都可以读取。除非将其加密,否则请勿将机密信息放入JWT的有效负载或报头元素中。

4.3 签名

签名时需要用到前面编码过的两个字符串,如果以 头部中指定的加密算法(HMACSHA256) 加密,就如下

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)

加密后再进行 base64url 编码最后得到的字符串就是 token 的第三部分

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

然后把这三部分使用.拼接在一起,就得到一个完整的JWT了

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发也是在服务端的,secret就是用来进行jwt的签发和jwt的验证,所以它就是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret,那就意味着客户端可以自我签发jwt了

我们一般是在请求头里加入Authorization,并加上Bearer标注,值就是JWT

headers: { 'Authorization': 'Bearer ' + token}

签名的作用: 保证 JWT 没有被篡改过,原理如下:

HMAC 算法是不可逆算法,类似 MD5 和 hash ,但多一个密钥,密钥(即上面的 secret)由服务端持有,客户端把 token 发给服务端后,服务端可以把其中的头部和载荷再加上事先共享的 secret 再进行一次 HMAC 加密,得到的结果和 token 的第三段进行对比,如果一样则表明数据没有被篡改。

5.JWT的总结

JWT的优点:

  • 因为json的通用性,所以JWT是可以跨语言支持的,像C#,JavaScript,NodeJS,PHP等许多语言都可以使用

  • 因为由了payload部分,所以JWT可以在自身存储一些其它业务逻辑所必要的非敏感信息

  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的

  • 它不需要在服务端保存会话信息,所以它易于应用的扩展安全相关

  • 不应该在jwt的payload部分存储敏感信息,因为该部分是客户端可解密的部分

  • 保护好secret私钥。该私钥非常重要

  • 如果可以,请使用https协议

6.JWT的java实现

1.导入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2.代码实现

    @Test
    public void testCreateToken(){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,90);
        JwtBuilder builder = Jwts.builder().setId("888").setSubject("zhangsan")
                .setIssuedAt(new Date())//设置签发时间
                .signWith(SignatureAlgorithm.HS256, "feisi")//使用HS256生成token,签名秘钥 则是feisi,唯一密钥的话可以保存在服务端。
                .setExpiration(instance.getTime());//设置过期时间
        System.out.println(builder.compact());
    }

生成的token内容:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJ6aGFuZ3NhbiIsImlhdCI6MTY5MTI0NTMzMSwiZXhwIjoxNjkxMjQ1NDIxfQ._aKLZld6rlf-Vt6xkdihFFJLpptbW4exvPBAttA2g4I

再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。

3.根据token和签名解析数据

我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

@Test
public void testProcessToke(){
    String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJ6aGFuZ3NhbiIsImlhdCI6MTY5MTI0NTMzMSwiZXhwIjoxNjkxMjQ1NDIxfQ._aKLZld6rlf-Vt6xkdihFFJLpptbW4exvPBAttA2g4I";
    Claims claims =
        Jwts.parser().setSigningKey("feisi").parseClaimsJws(token).getBody();
    System.out.println("id:"+claims.getId());
    System.out.println("subject:"+claims.getSubject());
    System.out.println("IssuedAt:"+claims.getIssuedAt());
    System.out.println("expiration:"+claims.getExpiration());
}

4.常见的异常信息:

  • ClaimJwtException:在验证JWT声明失败后抛出

  • ExpiredJwtException:表示JWT在过期后被接受,必须被拒绝

  • MalformedJwtException:当JWT未正确构造并且应该被拒绝时抛出

  • PrematureJwtException:表示JWT在被允许访问之前被接受,必须被拒绝

  • SignatureException:表示计算签名或验证JWT的现有签名失败

  • UnsupportedJwtException:在接收到与应用程序预期格式不匹配的特定格式/配置的JWT时抛出。例如,如果在应用程序需要加密签名的声明JWS时解析无符号明文JWT,则会抛出此异常

  • JJWT使用了许多其他Exception类。它们都可以在JJWT源代码中的io.jsonwebtoken包中找到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值