认证和授权

认证

认证 (Authentication): 服务器需要知道你是谁。
授权 (Authorization): 服务器需要知道你有权限干什么。

要解决的问题

用户认证和授权,在最开始依赖于Cookie和Session,但两种方法都存在一些弊端,
Cookie因为存放在用户浏览器中,安全性不能得到保证,别人通过获取到你的cookie值就可以实现 CSRF跨站攻击,而且目前Google在逐步将浏览器中的Cookie取代(2022年年底)
Session相较于Cookie,存放在服务器中,相较于Cookie安全性更高,但Session在本地同时依赖于Cookie,JSessionid和Sessionid就存放在Cookie中,如果浏览器禁止存取Cookie,那Session实现的登录状态就也会失效.Session在服务器是单机状态下,是非常有效的,当变成服务器集群后Session的存储和共享就会成为一个新的问题.

解决方案之一

Token就是解决以上问题的一种方案,将Token存放在客户端本地(Cookie, localStorage,请求头),在客户端发送请求时,携带该Token到服务端进行认证
一般使用请求头携带会较多一点,或者使用Post的请求体携带Token,因为Cookie和请求参数携带,需要解决跨域问题.
而Token和我们的jwt又有什么样的关系,这就是我们下面要解释的

JSON Web Token

通俗的来讲就是一段加密(BASE64)后的json字符串,并且还带有签名,使得我们可以使用它来进行身份验证和授权,我们常用的Token很多时候就是以jwt形式进行传输的,jwt就是Token的一种实现方式

jwt的组成

jwt一般由三部分构成

  1. Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型
  2. Payload:用来存放实际需要传递的数据
    1. 标准中注册的声明(建议但不强制使用)
      1. iss: jwt签发者
      2. sub: jwt所面向的用户
      3. aud: 接收jwt的一方
      4. exp: jwt的过期时间,这个过期时间必须要大于签发时间
      5. nbf: 定义在什么时间之前,该jwt都是不可用的.
      6. iat: jwt的签发时间
      7. jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
    2. 公共声明
      公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
    3. 私有的声明
      私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
      这个指的就是自定义的claim。比如下面面结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。
  3. Signature:服务器通过 Payload、Header 和一个密钥 (secret) 使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。

jwt的安全性保证

由于jwt的最后一段是服务器分发的加密签名,所以只要服务器的签名密钥不被泄漏,那就可以保证这个Token是安全的

jwt的缺点

  1. JWT 的缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑
  2. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

Java中的jwt-jjwt

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

JJWT的目标是最容易使用和理解用于在JVM上创建和验证JSON Web令牌(JWTs)的库。
JJWT是基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。
JJWT还添加了一些不属于规范的便利扩展,比如JWT压缩和索赔强制。

Maven

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

Gradle

implementation 'io.jsonwebtoken:jjwt:0.9.1'

签发签名

    JwtBuilder builder =
        Jwts.builder()
            .setId("888") // 设置唯一编号
            .setSubject("崔斯特") // 设置主题  可以是JSON数据
            .setIssuedAt(
                Date.from(
                    LocalDateTime.now().atZone(ZoneOffset.systemDefault()).toInstant())) // 设置签发日期
            .signWith(SignatureAlgorithm.HS256, "ipisce42"); // 设置签名 使用HS256算法,并设置SecretKey(字符串)
    // 构建 并返回一个字符串
    System.out.println(builder.compact());

解析jwt

  String compactJwt =
        "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLltJTmlq_nibkiLCJpYXQiOjE2MzQ4MDM5NTB9.MteN32inuZ7XNsfyfWQf0np0n8jnbseis9fs1fAezVY";
    Claims claims = Jwts.parser().setSigningKey("ipisce42").parseClaimsJws(compactJwt).getBody();
    var signature =
        Jwts.parser().setSigningKey("ipisce42").parseClaimsJws(compactJwt).getSignature();
    var header = Jwts.parser().setSigningKey("ipisce42").parseClaimsJws(compactJwt).getHeader();
    System.out.println(header);
    System.out.println(signature);
    System.out.println(claims);

自定义声明

  var map = new HashMap<String, Object>();
    map.put("sex", "男");
    map.put("nickname", "卡牌大师");
    JwtBuilder builder =
        Jwts.builder()
            .setId("888") // 设置唯一编号
            .setSubject("崔斯特") // 设置主题  可以是JSON数据
            .setIssuedAt(
                Date.from(
                    LocalDateTime.now().atZone(ZoneOffset.systemDefault()).toInstant())) // 设置签发日期
            .signWith(SignatureAlgorithm.HS256, "ipisce42");
    builder.addClaims(map);
    System.out.println(builder.compact());

Q.E.D.