session、token、cookie详解,和java JWT工具类

前言

session和token是当下两种流行的会话标志方法,近年来,随着微服务、分布式普遍,session的不利之处也越来越明显,占用内存多、无法跨服务器使用、易被跨域攻击等缺点使得大家越来越偏向于使用token,有些人会狡辩说token不是也有缺点吗?他们会说:无法在服务器端终止token,如果要做到这一功能必须要在服务端保存token才行,那么,和session有什么区别?是的,他们说的这些没错,但是token的利处明显大于弊处,对于当下的技术趋势而言,token一定程度会取代session,或者与session地位并列。

这篇文章主要想分析一下,对于我们为什么需要标识用户的会话,token、session是怎么进行这一过程的,还有探讨到底什么时候用token,什么时候用session。文章一同提供了一个java的token工具类。

从源头讲起

为什么需要session、token这些东西?这要从源头分析起,大家知道http的一个显著特征就是无状态!
无状态的原因是http是短连接,它接受请求、回复请求之后就会断开连接。因此前后两次连接之间并没有关联,它也不记录请求的ip。

无状态意味着,它不记录请求它的用户信息,它无法区分这次请求是来自那个用户。
缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。

另一方面,在服务器不需要先前信息时它的应答就较快。

如果我们做的是简单的静态网页,网页不需要区分用户,那么无状态是没有影响的,用户a请求网页1、用户b请求网页1,不管是用户a还是用户b,服务器都返回同一个网页1,;

但是如果我们要做一个网上书店,我们希望书店有普通用户,有管理员,http的无状态就行不通了,我们要做的是就是为每一次请求都带上状态,这很简单,我们可以设计好数据库表结构,用来存储用户和管理员。
然后在登录的时候判断是用户还是管理员,根据不同类型生成一个标志,比如如下这段伪代码

// 登录,返回一个身份标识字符串,前端将它存储,并在每次请求都附带上
login(name, password) {
	if (user = userDao.find(name)) {
		if (user.password == password) {
			// 密码校验正确
			if (user.isAdmin) {
			    // 管理员标志
				return "user-admin"
			}
			else {
			    // 用户标志
				return "user-" + user.id
			}
        }
        return "密码错误"
    }
    return "用户不存在"
}

// 删除书,敏感操作
deleteBook(request) {
	// 判断前端的身份标识token是不是管理员特有的
	if (request.token == "user-admin") {
		return doDelete(request.bookId)
	}
	else {
		return "没有权限"
	}
}

// 购买书,需要登录
buyBook(request) {
	// 获取用户id
	userId = int(request.token.subString(5))
    // 查找到用户
	if (user = userDao.find(userId)) {
		// 生成书订单
		// 用户扣款
		// 返回结果
	}
}

改进

这一段伪代码,大概表明了后端验证身份的思路。但是它太不安全。

  1. 管理员身份很容易伪造
  2. 存储的只是用户的标识,我们仍要花时间查表,查找用户信息
  3. 用户标识太简单,前端可以通过获取这个标识获取到用户的id信息
  4. 没有过期机制,取到了身份标识就可以一直用

基于此,我们提出以下要求:

  • 这个身份标识足够长,足够复杂,因此不会出现重复
  • 身份标识不应该包含用户的个人敏感信息,这样可以安心把它发到前端
  • 在服务器内存中维护一个map,存储身份标识对应的用户信息,这样就可以把常用的信息存储其中,不用每次都去查数据库
  • 必须要有过期机制

Session

👆满足上述这些要求的就是我们最常用的session

  1. web服务器维护每一个用户的sessionId 和 用户对应的信息的映射
  2. 一个新用户请求时,web服务器判断cookie中是否有sessionId,如果有则设置请求的session为sessionId映射的用户信息,如果没有,则web服务器生成一个sessionId,这个sessionId足够复杂,因此不会重复
  3. 应用程序从HttpSession中获取到用户的session,并可以往其中存数据和取数据,比如在管理员登录时,设置httpSession.isAdmin = true,这样如果需要判断管理员权限的接口,只需要判断httpSession.isAdmin是否存在、是否为真就行了。
  4. 会话一般都有一个有效时间,一但超出有效时间,web服务器会自动删除该会话。也有根据用户访问自动续时的。
  5. 会话被删除后,一般就需要重新进行登录

常用的web服务器有tomcat、jboss、apache、nginx等

session的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

Cookie

上边提到的cookie,也是用来存储状态信息的方式,前端浏览器将后端发放的cookie保存下来,并在每一次请求的时候附带上,后端就可以通过这种方式设置和获取一些用户信息,但是由于cookie并没有加密(你可以自己加密)所以并不安全。

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

Cookie的不可跨域名性
很多网站都会使用Cookie。例如,Google会向客户端颁发Cookie,Baidu也会向客户端颁发Cookie。那浏览器访问Google会不会也携带上Baidu颁发的Cookie呢?或者Google能不能修改Baidu颁发的Cookie呢?

答案是否定的。Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。

Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样,因此Google不能操作Baidu的Cookie。

cookie并不是单纯为了实现 session机制而生的。而是1993 年,网景公司雇员 Lou Montulli 为了让用户在访问某网站时,进一步提高访问速度,同时也为了进一步实现个人化网络,发明了今天广泛使用的 Cookie。cookie还用一个很广泛的用途就是记住用户的登录账号和密码,这样当用户以后再次需要登录同一个网站或系统的时候就不需要再次填写这两个字段而是直接点击“登录”按钮就好。这就相当于给了一些“甜头”给用户,这就回应了“cookie”这个词语的字面意思了。

JWT token

JWT 和 session的思路完全不同,却和cookie有相似之处。
JWT 全称为 JSON Web Token,它是基于JSON进行数据传输的。

以下内容引用 阮一峰

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
实际的 JWT 大概就像下面这样。
在这里插入图片描述
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT 的三个部分依次如下。

Header(头部).
Payload(负载).
Signature(签名)

写成一行,就是下面的样子。

Header.Payload.Signature

在这里插入图片描述

Header 部分

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
  "alg": "HS256",
  "typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

Payload 部分

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

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 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

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

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。


Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

JWT 的几个特点

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

Java JWT使用

需要导入MAVEN工具类:

        <!--jwt组件-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

以下,是JWT的核心类,可以看到包含了decode(对字符串token进行解码)、require(生成签名验证器)、create(返回一个token生成器)这三大部分组成的:

package com.auth0.jwt;

import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;

@SuppressWarnings("WeakerAccess")
public abstract class JWT {

    /**
     * Decode a given Json Web Token.
     * <p>
     * Note that this method <b>doesn't verify the token's signature!</b> Use it only if you trust the token or you already verified it.
     *
     * @param token with jwt format as string.
     * @return a decoded JWT.
     * @throws JWTDecodeException if any part of the token contained an invalid jwt or JSON format of each of the jwt parts.
     */
    public static DecodedJWT decode(String token) throws JWTDecodeException {
        return new JWTDecoder(token);
    }

    /**
     * Returns a {@link JWTVerifier} builder with the algorithm to be used to validate token signature.
     *
     * @param algorithm that will be used to verify the token's signature.
     * @return {@link JWTVerifier} builder
     * @throws IllegalArgumentException if the provided algorithm is null.
     */
    public static Verification require(Algorithm algorithm) {
        return JWTVerifier.init(algorithm);
    }

    /**
     * Returns a Json Web Token builder used to create and sign tokens
     *
     * @return a token builder.
     */
    public static JWTCreator.Builder create() {
        return JWTCreator.init();
    }
}

核心的签名函数,可以看到,首先是对header、payload进行了BASE64URL编码,然后String.format("%s.%s", header, payload),然后调用预设的算法进行签名,然后对签名也进行BASE64URL编码,最后String.format("%s.%s", content, signature)返回生成的token。

    private String sign() throws SignatureGenerationException {
        String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
        String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
        String content = String.format("%s.%s", header, payload);

        byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
        String signature = Base64.encodeBase64URLSafeString((signatureBytes));

        return String.format("%s.%s", content, signature);
    }

接下分三个部分,讲JWT的使用:

1、生成

JWT是链式模式的典型,以下是生成token的代码:

// 生成token
        /*
        负载的7个默认字段:
            iss (issuer):签发人
            exp (expiration time):过期时间
            sub (subject):主题
            aud (audience):受众
            nbf (Not Before):生效时间
            iat (Issued At):签发时间
            jti (JWT ID):编号
         */
        // 自定义header
        Map<String, Object> header = new HashMap<>(1);
        header.put("web", "www.hengyumo.cn");

        String token = JWT.create()

                // Header(头部)
                .withHeader(header)

                // Payload(负载)
                .withIssuer("签发人-衡与墨")
                .withExpiresAt(DateUtil.addDay(new Date(), 2))
                .withSubject("主题-示例")
                .withAudience("受众1", "受众2")
                .withNotBefore(DateUtil.addHour(new Date(), 2))
                .withIssuedAt(new Date())
                .withJWTId("编号1")
                // 自定义要求项
                .withClaim("isAdmin", false)
                .withClaim("userName", "xxx")
                .withArrayClaim("books", new String[] { "书1", "书2" })

                // Signature(签名)
                // 可选算法有 RSA256、RSA384、RSA512、HMAC256、HMAC384、HMAC512、ECDSA256
                // ECDSA384、ECDSA512、或者 none (不签名)
                .sign(Algorithm.HMAC256("asdfq2@13asf"));

        System.out.println("生成token:" + token);

        String[] splits = token.split("\\.");
        // 这里用了Spring的Base64Utils进行Base64URL编码的解码
        String headerJson = new String(Base64Utils.decodeFromUrlSafeString(splits[0]));
        System.out.println("headerJson:" + headerJson);
        String payload = new String(Base64Utils.decodeFromUrlSafeString(splits[1]));
        System.out.println("payloadJson:" + payload);
        String signature = new String(Base64Utils.decodeFromUrlSafeString(splits[2]));
        System.out.println("signature:" + signature);

输出:

生成token:eyJ3ZWIiOiJ3d3cuaGVuZ3l1bW8uY24iLCJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiLkuLvpopgt56S65L6LIiwiYXVkIjpbIuWPl-S8lzEiLCLlj5fkvJcyIl0sIm5iZiI6MTU3MzU1NjIwOSwiYm9va3MiOlsi5LmmMSIsIuS5pjIiXSwiaXNzIjoi562-5Y-R5Lq6LeihoeS4juWiqCIsImlzQWRtaW4iOmZhbHNlLCJleHAiOjE1NzM3MjE4MDksInVzZXJOYW1lIjoieHh4IiwiaWF0IjoxNTczNTQ5MDA5LCJqdGkiOiLnvJblj7cxIn0.fco8jy9qZpbn8QcIrimcW5FjPsrczhEKJZGlz-dhpUI
headerJson:{"web":"www.hengyumo.cn","alg":"HS256","typ":"JWT"}
payloadJson:{"sub":"主题-示例","aud":["受众1","受众2"],"nbf":1573556209,"books":["书1","书2"],"iss":"签发人-衡与墨","isAdmin":false,"exp":1573721809,"userName":"xxx","iat":1573549009,"jti":"编号1"}
signature:}</jf����)[�c>���
%����a�B

可以看到,token的三个部分确实是用.分离的,并且header、payload都是使用的json传输。
header、payload很容易就能被解码,因此我们不能将重要的数据放入,除非我们放入数据之前先进行加密。

2、验证

验证token的流程是先生成一个验证器,指名要验证的字段,然后会先对签名进行验证,之后判断过期时间还有Claim字段,以下是源码中验证部分的核心代码,重点在抛出的几个异常:


    /**
     * Perform the verification against the given Token, using any previous configured options.
     *
     * @param token to verify.
     * @return a verified and decoded JWT.
     * @throws AlgorithmMismatchException     if the algorithm stated in the token's header it's not equal to the one defined in the {@link JWTVerifier}.
     * @throws SignatureVerificationException if the signature is invalid.
     * @throws TokenExpiredException          if the token has expired.
     * @throws InvalidClaimException          if a claim contained a different value than the expected one.
     */
    public DecodedJWT verify(String token) throws JWTVerificationException {
        DecodedJWT jwt = JWT.decode(token);
        verifyAlgorithm(jwt, algorithm);
        algorithm.verify(jwt);
        verifyClaims(jwt, claims);
        return jwt;
    }

以下是使用它进行验证的代码:

        // token 验证、构造验证器
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("asdfq2@13asf"))
                .withClaim("web", "www.hengyumo.cn")
                .withIssuer("签发人-衡与墨")
                .withClaim("isAdmin", false)
                .build();

        try {
            DecodedJWT decodedJwt = jwtVerifier.verify(token);
        }
        catch (AlgorithmMismatchException ame) {
            System.out.println("token 签名算法不符合");
        }
        catch (SignatureVerificationException sve) {
            System.out.println("token 签名错误");
        }
        catch (TokenExpiredException tee) {
            System.out.println("token 已经失效");
        }
        catch (InvalidClaimException ice) {
            System.out.println("字段错误," + ice.getMessage());
        }

输出

字段错误,The Token can't be used before Tue Nov 12 19:14:12 CST 2019.

因为使用的是1、中生成的token,所以还未达到token的生效时间。
如果没有抛出异常就代表验证成功。

3、解析

        // token解析
        DecodedJWT decodedJwt = JWT.decode(token);
        // 没有重载toString 方法
        System.out.println(decodedJwt);
        System.out.println("Audience:" + decodedJwt.getAudience());
        System.out.println("Claims:" + decodedJwt.getClaims());

输出

com.auth0.jwt.JWTDecoder@3b2da18f
Audience:[受众1, 受众2]
Claims:{sub=com.auth0.jwt.impl.JsonNodeClaim@78b1cc93, aud=com.auth0.jwt.impl.JsonNodeClaim@6646153, nbf=com.auth0.jwt.impl.JsonNodeClaim@21507a04, books=com.auth0.jwt.impl.JsonNodeClaim@143640d5, iss=com.auth0.jwt.impl.JsonNodeClaim@6295d394, isAdmin=com.auth0.jwt.impl.JsonNodeClaim@475e586c, exp=com.auth0.jwt.impl.JsonNodeClaim@657c8ad9, userName=com.auth0.jwt.impl.JsonNodeClaim@436a4e4b, iat=com.auth0.jwt.impl.JsonNodeClaim@f2f2cc1, jti=com.auth0.jwt.impl.JsonNodeClaim@3a079870}

打个断点,看一下字段数据:
在这里插入图片描述

JWT Java工具类

这个工具类更多是提供思路,完全可以根据项目的情况定制工具类

package cn.hengyumo.humor.utils;

import cn.hengyumo.humor.utils.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.util.Base64Utils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * JwtUtil
 * JWT工具类、使用HMAC256签名
 *
 * @author hengyumo
 * @version 1.0
 * @since 2019/11/12
 */
@SuppressWarnings("unused")
public class JwtUtil {

    /**
     * 生成token
     *
     * @param audience 用户信息(注意别放置重要信息),可以放置用户id或者用户身份标识字符串(存在redis缓存)
     * @param key      加密密钥
     * @param expireMinutes 过期时间,分钟
     *
     * @return token
     * @throws JWTCreationException     if the claims could not be converted to a valid JSON
                                        or there was a problem with the signing key.
     */
    public static String create(String audience, String key, int expireMinutes) {
        return JWT.create()
                    .withAudience(audience)
                    .withExpiresAt(DateUtil.addMinute(new Date(), expireMinutes))
                    .sign(Algorithm.HMAC256(key));
    }

    /**
     * 生成不过期token
     *
     * @param audience 用户信息(注意别放置重要信息),可以放置用户id或者用户身份标识字符串(存在redis缓存)
     * @param key      加密密钥
     *
     * @return token
     * @throws JWTCreationException     if the claims could not be converted to a valid JSON
                                        or there was a problem with the signing key.
     */
    public static String create(String audience, String key) {
        return JWT.create()
                .withAudience(audience)
                .sign(Algorithm.HMAC256(key));
    }

    /**
     * token 验证
     *
     * @param token    token
     * @param audience 用户信息(注意别放置重要信息),可以放置用户id或者用户身份标识字符串(存在redis缓存)
     * @param key      加密密钥
     *
     * @return DecodedJWT
     * @throws AlgorithmMismatchException     if the algorithm stated in the token's header it's not equal
                                                to the one defined in the {@link JWTVerifier}.
     * @throws SignatureVerificationException if the signature is invalid.
     * @throws TokenExpiredException          if the token has expired.
     * @throws InvalidClaimException          if a claim contained a different value than the expected one.
     */
    public static DecodedJWT verify(String token, String audience, String key) {
        return JWT.require(Algorithm.HMAC256(key))
                    .withAudience(audience)
                    .build()
                    .verify(token);
    }

    /**
     * 解析token
     *
     * @param token token
     * @return DecodedJWT
     */
    public static DecodedJWT decode(String token) {
        return JWT.decode(token);
    }


    /**
     * 测试
     *
     * @param args args
     */
    public static void main(String[] args) throws InterruptedException {
        // 生成token
        /*
        负载的7个默认字段:
            iss (issuer):签发人
            exp (expiration time):过期时间
            sub (subject):主题
            aud (audience):受众
            nbf (Not Before):生效时间
            iat (Issued At):签发时间
            jti (JWT ID):编号
         */
        // 自定义header
        Map<String, Object> header = new HashMap<>(1);
        header.put("web", "www.hengyumo.cn");

        String token = JWT.create()

                // Header(头部)
                .withHeader(header)

                // Payload(负载)
                .withIssuer("签发人-衡与墨")
                .withExpiresAt(DateUtil.addDay(new Date(), 2))
                .withSubject("主题-示例")
                .withAudience("受众1", "受众2")
                .withNotBefore(DateUtil.addHour(new Date(), 2))
                .withIssuedAt(new Date())
                .withJWTId("编号1")
                // 自定义要求项
                .withClaim("isAdmin", false)
                .withClaim("userName", "xxx")
                .withArrayClaim("books", new String[] { "书1", "书2" })

                // Signature(签名)
                // 可选算法有 RSA256、RSA384、RSA512、HMAC256、HMAC384、HMAC512、ECDSA256
                // ECDSA384、ECDSA512、或者 none (不签名)
                .sign(Algorithm.HMAC256("asdfq2@13asf"));

        System.out.println("生成token:" + token);

        String[] splits = token.split("\\.");
        // 这里用了Spring的Base64Utils进行Base64URL编码的解码
        String headerJson = new String(Base64Utils.decodeFromUrlSafeString(splits[0]));
        System.out.println("headerJson:" + headerJson);
        String payload = new String(Base64Utils.decodeFromUrlSafeString(splits[1]));
        System.out.println("payloadJson:" + payload);
        String signature = new String(Base64Utils.decodeFromUrlSafeString(splits[2]));
        System.out.println("signature:" + signature);

        // token 验证
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("asdfq2@13asf"))
                .withClaim("web", "www.hengyumo.cn")
                .withIssuer("签发人-衡与墨")
                .withClaim("isAdmin", false)
                .build();

        DecodedJWT decodedJwt;
        try {
            jwtVerifier.verify(token);
        }
        catch (AlgorithmMismatchException ame) {
            System.out.println("token 签名算法不符合");
        }
        catch (SignatureVerificationException sve) {
            System.out.println("token 签名错误");
        }
        catch (TokenExpiredException tee) {
            System.out.println("token 已经失效");
        }
        catch (InvalidClaimException ice) {
            System.out.println("字段错误," + ice.getMessage());
        }

        // token解析
        decodedJwt = JWT.decode(token);
        // 没有重载toString 方法
        System.out.println(decodedJwt);
        System.out.println("Audience:" + decodedJwt.getAudience());
        System.out.println("Claims:" + decodedJwt.getClaims());

        // 验证工具类

        // 生成token
        token = create("衡与墨", "hengyumo@1234", 1);

        // 正确
        verify(token, "衡与墨", "hengyumo@1234");

        try {
            verify(token, "哼", "hengyumo@1234");
        }
        catch (InvalidClaimException ice) {
            System.out.println("字段错误," + ice.getMessage());
        }

        try {
            verify(token.substring(4), "衡与墨", "hengyumo@1234");
        }
        catch (SignatureVerificationException sve) {
            System.out.println("token 签名错误");
        }
        catch (JWTDecodeException jde) {
            System.out.println("异常," + jde.getMessage());
        }

        try{
            TimeUnit.MINUTES.sleep(1);
            verify(token, "衡与墨", "hengyumo@1234");
        }
        catch (TokenExpiredException tee) {
            System.out.println("token 已经失效");
        }
    }
}

参考&引用

JSON Web Token 入门教程
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
什么是Http无状态?Session、Cookie、Token三者之间的区别
https://www.cnblogs.com/lingyejun/p/9282169.html

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值