当前位置:首页 » 《关于电脑》 » 正文

JSON Web Token (JWT): 理解与应用

13 人参与  2024年11月20日 12:02  分类 : 《关于电脑》  评论

点击全文阅读


JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。JWT通常用于身份验证和授权目的,因为它可以使用JSON对象在各方之间安全地传输信息

官网地址:https://jwt.io/

0.介绍

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

0.1.JWT的应用场景

身份验证 : 当用户成功登录时,服务器会生成一个JWT并将其发送给客户端。客户端在后续请求中将JWT附加到HTTP请求头中,以此来证明用户的身份。

授权 : JWT中可以包含用户的权限信息,这样服务器可以根据这些信息决定用户是否被允许访问某些资源。

信息传递 : 除了用户的身份和权限外,JWT还可以用来携带其他有用的信息,如用户的偏好设置等。

在 java 中 常与 Spring Security 框架配合使用

JWT的认证流程如下:

首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

在这里插入图片描述

0.2.优点

这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:

支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题

无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力

更适用CDN:可以通过内容分发网络请求服务端的所有资料

更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多

无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

1.JWT的结构

JWT是一种自包含的令牌格式。JWT由三个部分组成:头部、载荷和签名。

在这里插入图片描述

1.1.头部 (Header)

头部通常包含两个部分:

typ: 表示该令牌的类型,通常是“JWT”。alg: 指定签名算法,例如 HMAC SHA-256 或 RSA。

头部通常以JSON格式书写,并经过Base64Url编码。

1.2.载荷 (Payload)

载荷包含了需要作为声明传输的信息。这些声明可以分为三类:

标准声明:由JWT规范定义的声明。 iss (issuer): 发行者。sub (subject): 主题,通常是指用户ID。aud (audience): 接收者,即令牌的预期受众。exp (expiration time): 过期时间。nbf (not before): 该时间之前不可使用。iat (issued at): 发行时间。jti (JWT ID): 一个唯一的标识符,用于防止重放攻击。 私有声明:由发行者和接收者约定的声明,例如用户的角色或权限等。公共声明:虽然不是JWT规范的一部分,但可以在任何JWT中使用。

载荷也是经过Base64Url编码的。

1.3.签名 (Signature)

签名部分保证了JWT的完整性和安全性。签名通过将头部和载荷进行编码并使用指定的算法(如HMAC SHA-256 RSA或ECDSA)进行计算得到。签名确保了:

令牌没有被篡改。令牌是由可信的一方发行的。

签名部分同样经过Base64Url编码。

1.4.JWS, JWK

JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS

为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。

加密的算法一般有2类:

对称加密:secretKey指加密密钥,可以生成签名与验签非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)

JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK

到目前为止,jwt的签名算法有三种:

HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512RSASSA【RSA签名算法(非对称)】 :(RS256/RS384/RS512)ECDSA【椭圆曲线数据签名算法(非对称)】 :(ES256/ES384/ES512)

2.实现与工具

2.1.jjwt (Java JWT)

GitHub 仓库地址:https://github.com/jwtk/jjwt

这是一个基于Java的库,用于在JVM和Android平台上创建和验证JSON Web Tokens (JWTs)和JSON Web Keys (JWKs)。

它支持多种JOSE工作组的RFC规范:

RFC 7519: JSON Web Token (JWT)RFC 7515: JSON Web Signature (JWS)RFC 7516: JSON Web Encryption (JWE)等等
2.1.1.导入Maven依赖
<dependency>    <groupId>io.jsonwebtoken</groupId>    <artifactId>jjwt-api</artifactId>    <version>0.11.5</version></dependency><dependency>    <groupId>io.jsonwebtoken</groupId>    <artifactId>jjwt-impl</artifactId>    <version>0.11.5</version></dependency><dependency>    <groupId>io.jsonwebtoken</groupId>    <artifactId>jjwt-jackson</artifactId>    <version>0.11.5</version></dependency>
2.1.2.测试代码
import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import io.jsonwebtoken.security.Keys;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.security.Key;import java.util.Date;import java.util.UUID;@RestControllerpublic class JwtController {    // 密钥 : 实际开发时 应该从 配置文件 / 持久化存储中 获取    private String secret;    // token时效:24小时    public static final long EXPIRE = 1000 * 60 * 60 * 24;    @RequestMapping("/create")    public String createToken(){        // 生成一个随机的密钥 ID        String keyId = UUID.randomUUID().toString();        // 密钥        secret = keyId;        // 创建一个 JWT 令牌        String jwt = Jwts.builder()                // 设置JWT头部参数,指定令牌类型为JWT                .setHeaderParam("typ", "JWT")                // 设置JWT头部参数,指定签名算法为HS256                .setHeaderParam("alg", "HS256")                // 设置JWT过期时间,当前时间戳加上设定的过期时长                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))                // 设置主题,通常是用户身份标识                .setSubject("admin")                            // 添加角色声明                .claim("role", "admin")                // 设置用户ID为1                .claim("id", 1)                // 设置用户昵称为"王小二"                .claim("nickname", "王小二")                            // 设置令牌签发时间                .setIssuedAt(new Date())                // 设置唯一标识符                .setId( keyId )                // 使用HS256算法和密钥签名                .signWith(SignatureAlgorithm.HS256, secret.getBytes())                // 将令牌压缩为紧凑形式的字符串                .compact();        return jwt;    }    @RequestMapping("/check")    public  void checkToken(String jwt) {        System.out.println("jwt = " + jwt);        // 创建一个安全的密钥        Key secureKey = Keys.hmacShaKeyFor(secret.getBytes());        try {            // 使用Jwts.parserBuilder()方法构建一个解析器,该解析器使用secureKey作为签名密钥            // 然后使用这个解析器解析jwt字符串,获取到Claims对象,即JWT的主体部分            Claims claims = Jwts.parserBuilder()                    .setSigningKey(secureKey)                    .build()                    .parseClaimsJws(jwt)                    .getBody();            // 打印解析后的 JWT 信息            System.out.println("Subject: " + claims.getSubject());            System.out.println("Role: " + claims.get("role", String.class));            System.out.println("Issue Time: " + claims.getIssuedAt());            System.out.println("JWT ID: " + claims.getId());            System.out.println("user nickname: " + claims.get("nickname"));            // 可以在这里添加更多逻辑来验证 JWT 的有效性,例如检查过期时间等            if(claims.getExpiration()==null){                System.out.println("过期时间不能为空 Expiration time cannot be null");            }        } catch (Exception e) {            // 如果 JWT 无法验证,这里会捕获异常            System.err.println("Invalid JWT: " + e.getMessage());        }    }}

2.2.Nimbus

Nimbus JOSE + JWT 是一个非常强大的 Java 库,用于处理 JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), JSON Web Token (JWT) 和 OAuth 2.0 授权服务器。这个库是由 NimbusDS 开发的,并且广泛应用于身份验证和授权系统中。

2.2.1.导入依赖

首先,您需要在 Maven 项目中添加 json-jwt 的依赖:

        <dependency>            <groupId>com.nimbusds</groupId>            <artifactId>nimbus-jose-jwt</artifactId>            <version>9.40</version>        </dependency>
2.2.2.测试代码
import com.nimbusds.jose.JOSEException;import com.nimbusds.jose.JWSAlgorithm;import com.nimbusds.jose.JWSHeader;import com.nimbusds.jose.crypto.MACSigner;import com.nimbusds.jose.crypto.MACVerifier;import com.nimbusds.jwt.JWTClaimsSet;import com.nimbusds.jwt.SignedJWT;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.text.ParseException;import java.util.Date;import java.util.UUID;@RestControllerpublic class JwtController {    // 密钥 : 实际开发时 应该从 配置文件 / 持久化存储中 获取    private String secret;    @RequestMapping("/create")    public String createToken() {        try {            // 生成一个随机的密钥 ID            String keyId = UUID.randomUUID().toString();            // 密钥            secret = keyId;            // 创建一个 HMAC 签名器            MACSigner signer = new MACSigner(secret.getBytes());            // 设置 JWT 的声明            JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();            builder.subject("admin");            builder.claim("role", "admin");            builder.issueTime(new Date());            builder.jwtID(keyId);            // 构建 JWT Claims Set            JWTClaimsSet claimsSet = builder.build();            // 创建一个空的 SignedJWT 对象            SignedJWT signedJWT = new SignedJWT(                    new JWSHeader(JWSAlgorithm.HS256),                    claimsSet);            // 签名 JWT            signedJWT.sign(signer);            // 将 JWT 转换为紧凑形式            String jwt = signedJWT.serialize();            return jwt;        } catch (JOSEException e) {            e.printStackTrace();        }        return null;    }    @RequestMapping("/check")    public void checkToken(String jwt) {        try {            System.out.println("jwt = " + jwt);            // 解析 JWT            SignedJWT signedJWT = SignedJWT.parse(jwt);            // 验证 JWT            MACVerifier verifier = new MACVerifier( secret.getBytes() );            boolean isValid = signedJWT.verify(verifier);            if (!isValid) {                System.out.println("无效的 JWT 签名 Invalid JWT signature.");                return;            }            // 获取 JWT Claims            JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();            System.out.println("Subject: " + claimsSet.getSubject());            System.out.println("Role: " + claimsSet.getStringClaim("role"));            System.out.println("Issue Time: " + claimsSet.getIssueTime());            System.out.println("JWT ID: " + claimsSet.getJWTID());        } catch (JOSEException | ParseException e) {            e.printStackTrace();        }    }        /**    * token 通过 header传递    * 再通过 request 取出    */    @RequestMapping("/checkHeader")    public void checkHeaderToken(HttpServletRequest request) {        try {            String jwt = request.getHeader("Authorization");            System.out.println("jwt header=> " + jwt);            // 解析 JWT            SignedJWT signedJWT = SignedJWT.parse(jwt);            // 验证 JWT            MACVerifier verifier = new MACVerifier( secret.getBytes() );            boolean isValid = signedJWT.verify(verifier);            if (!isValid) {                System.out.println("无效的 JWT 签名 Invalid JWT signature.");                return;            }            // 获取 JWT Claims            JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();            System.out.println("Subject: " + claimsSet.getSubject());            System.out.println("Role: " + claimsSet.getStringClaim("role"));            System.out.println("Issue Time: " + claimsSet.getIssueTime());            System.out.println("JWT ID: " + claimsSet.getJWTID());        } catch (JOSEException | ParseException e) {            e.printStackTrace();        }    }}
2.2.3.请求测试
###GET http://localhost:8080/create

生成token : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyMzQ1MDgyOCwiaWF0IjoxNzIzMzY0NDI4LCJqdGkiOiI5Yzc3N2YwZC03NDU5LTQ3MTUtYmVkNy1mNWViYzJiNmMwOTgifQ.Y_P7L4gehl0kJwxTnwUxX8Yy502qrCHQ0hkxod58ly8

###GET http://localhost:8080/check?jwt=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyMzQ1MDgyOCwiaWF0IjoxNzIzMzY0NDI4LCJqdGkiOiI5Yzc3N2YwZC03NDU5LTQ3MTUtYmVkNy1mNWViYzJiNmMwOTgifQ.Y_P7L4gehl0kJwxTnwUxX8Yy502qrCHQ0hkxod58ly8

idea 控制台:
Subject: admin
Role: admin
Issue Time: Sun Aug 11 16:20:28 CST 2024
JWT ID: 9c777f0d-7459-4715-bed7-f5ebc2b6c098

###GET http://localhost:8080/checkHeaderContent-Type: application/jsonAuthorization: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyMzQ1MDgyOCwiaWF0IjoxNzIzMzY0NDI4LCJqdGkiOiI5Yzc3N2YwZC03NDU5LTQ3MTUtYmVkNy1mNWViYzJiNmMwOTgifQ.Y_P7L4gehl0kJwxTnwUxX8Yy502qrCHQ0hkxod58ly8

idea 控制台:
Subject: admin
Role: admin
Issue Time: Sun Aug 11 16:20:28 CST 2024
JWT ID: 9c777f0d-7459-4715-bed7-f5ebc2b6c098


点击全文阅读


本文链接:http://zhangshiyu.com/post/189186.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1