你真的了解JWT吗

1.JWT官网介绍

What is JSON Web Token?

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为JSON对象安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWTS可以使用密钥(使用HMAC算法)或公钥/私钥对使用RSA或ECDSA来签名。虽然JWTS可以加密,但也提供保密各方之间,我们将重点放在签名令牌。签名的令牌可以验证其中包含的声明的完整性,而加密的令牌则向其他方隐藏这些声明。当令牌使用公钥/私钥对签名时,签名还证明只有持有私钥的一方是对其签名的一方。

说白了,JWT可以算是一种生成Token的加密算法,我们可以将信息存储在令牌(Token)当中,并且,持有密钥的一方才可以进行验证Token的有效性。

2.为什么要使用JWT?

2.1 传统的Session认证

由于Http协议是无状态的,所以服务端无法识别每一次请求是从哪个用户发来的。

所以在传统的开发当中,都会采取以下类似的操作,假设我们要对用户进行鉴权操作,大概有如下:
1.用户在客户端输入账号密码进行登陆
2.服务端对账号密码进行校验,校验成功则将用户信息存储在服务端,并生成一串session_id返回给客户端存储在Cookie当中
3.用户再次访问或操作时,只需将Cookie中的session_id发送给服务端即可完成鉴权。

2.2 Session认证的缺点

1.当大量用户信息存储在服务端时,会使服务器开销增大,若采用集群或分布式的情况,还要考虑到Session的共享机制。
2.存在跨站请求伪造攻击的风险(CSRF)

跨站请求伪造:假设你在A系统进行转账,之后你去访问存在风险的B系统,B系统可以将你的session_id进行转发,从而盗取你的血汗钱。服务端通过session_id对账户进行识别,一旦session_id泄露,会造成不可预估的风险。

2.3 JWT出现了

JWT的鉴权机制类似于Http协议,也是属于无状态的。我们可以将用户的非敏感信息存储在Token中,在每次请求的过程中,携带在Header并发送给服务端,服务端使用密钥进行解析,从而可以得到用户的相关信息,这时候就避免了过多的信息存储在Session中,降低服务端的开销。

2.4 JWT组成

JWT由三部分组成:Header、Payload、Signature
JWT都长这样:liang.zai.bo
liang代表Header,zai代表Payload,bo代表Signature

头部(Header): 头部用于描述Json Web Token的基本信息,如类型、签名算法。将以下JSON格式的字符串进行Base64编码,就得到了Header。

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

载荷(Payload):载荷是JWT的主体内容部分,里面存放着一些非敏感信息,官网定义了以下五个字段:iss、sub、aud、exp、iat
iss:JWT的签发者
sub:JWT的主体部分
aud:接收JWT的一方
exp:该JWT的过期时间,格式为Unix时间戳(一定要注意!不然就是个坑)
iat:JWT的签发时间
将以下的JSON字符串进行Base64编码,即可得到Payload。

{
    
    
	"sub":"liangzaibo",
	"exp":"666666666666666666666",
	"username":"liangzaibo",// 我们还可以自定义字段
}

签名(Signature):将Header和Payload进行Base64编码以及提供的密钥,采用Header指定的签名算法进行加密,即HS256(Base64Encode(Header) + “.” + Base64Encode(Payload),SECRET)

2.5 JWT实践

public class Main {
    
    
    public static void main(String[] args) throws ParseException {
    
    
        // header 可有可无,此处为了方便演示,默认为{"alg":"HS256"}
        Map<String, Object> header = new HashMap<String, Object>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");

        // claims代表Payload的主要内容,可以自定义字段
        Map<String, Object> cliams = new HashMap<String, Object>();
        cliams.put("username", "liangzaibo");

        String token = generateJwtToken(header, cliams);
        System.out.println(token);
        //eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
	    //eyJleHAiOjE2MTAyMDgwMDAsImlhdCI6MTYwOTQ5ODk3OSwidXNlcm5hbWUiOiJsaWFuZ3phaWJvIn0.
		//vglPuCoRdTQbVq68TykiDsiev-p_Rr61WQvJHwqOLWM


        Claims claims = parseJwtToken(token);
        System.out.println(claims);
        // {exp=1610208000, iat=1609498979, username=liangzaibo}
    }

    /**
     * 生成JWT令牌
     */
    public static String generateJwtToken(Map<String,Object> header, Map<String,Object> claims) throws ParseException {
    
    
        return Jwts.builder()
                .setHeader(header)
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS256, "SECRET") //加密算法以及密钥
                .setExpiration(new SimpleDateFormat("yyyy-MM-dd").parse("2021-01-10")) // JWT过期时间
                .setIssuedAt(new Date()) // 签发时间
                .compact();
    }

    /**
     * 解析JWT
     */
    public static Claims parseJwtToken(String token){
    
    
        return Jwts.parser()
                .setSigningKey("SECRET")
                .parseClaimsJws(token)
                .getBody();
    }

2.6 注意事项

  • 不得将敏感信息存储在JWT中,因为Header、Payload只是进行了一次Base64编码,所以只要对Header、Payload进行一次Base64解码即可得到内部信息。
  • JWT是支持跨语言的,但是我遇到一个项目,开发人员设置JWT的过期时间不是时间戳格式,导致最后我无法进行同步解析。所以一定要注意,存储JWT的exp过期时间一定要为时间戳。否则最后是无法解析的。
  • 生成JWT时,过期时间不易设置过长,因为JWT一旦签发,就无法撤销,必须等待其到达过期时间才失效。
  • 并不是所有场合都适用JWT,但业务不需要进行加密解密时,使用JWT就是累赘。

猜你喜欢

转载自blog.csdn.net/qq_41917138/article/details/112061805