JWT介绍

  • 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于将信息作为 JSON 对象在各方之间安全地传输。该信息可以进行验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
  • JWT只是缩写,全拼则是 JSON Web Tokens ,是目前流行的跨域认证解决方案,一种基于JSON的、用于在网络上声明某种主张的令牌(token)。

JWT 优缺点

  • 优点
    • 支持跨域访问
    • 基于 token 的认证方式相比传统的 session 认证方式更节约服务器资源
    • 无状态
    • 更适用CDN
    • 更适用于移动端
    • 无需考虑CSRF
  • 缺点
    • 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
    • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

JWT 结构

JWT 是由三段字符串和两个 . 组成,每个字符串和字符串之间没有换行(类似于这样:xxxxxx.yyyyyy.zzzzzz),每个字符串代表了不同的功能,我们将这三个字符串的功能按顺序列出来并讲解:

标头(header)

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

{“alg”: “HS256”,“typ”: “JWT”}

使用 Base64 URL 算法将该 JSON 对象转换为字符串保存,形成 JWT 的第一部分。

有效载荷(payload)

JWT 的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的语句。有三种类型的声明:注册声明、公共声明和私人声明。

  • 标准注册声明
    标准注册声明不是强制使用是的,但是我建议使用。它一般包括以下内容:
iss:#jwt的签发者/发行人;sub#主题;aud:#接收方;exp:#jwt过期时间;nbf:#jwt生效时间;iat:#签发时间jti:#jwt唯一身份标识,可以避免重放攻击
  • 公共声明
    可以在公共声明添加任何信息,我们一般会在里面添加用户信息和业务信息,但是不建议添加敏感信息,因为公共声明部分可以在客户端解密。

  • 私有声明
    私有声明是服务器和客户端共同定义的声明,同样这里不建议添加敏感信息。
    以下是有效负载的示例:

{“sub”: “1234567890”,“name”: “John Doe”,“admin”: true}

签名(Signature)

要创建签名部分,必须获取编码的标头、编码的有效负载、密钥,通过指定的算法生成哈希,以确保数据不会被篡改。
例如,如果要使用 HMAC SHA256 算法,将按以下方式创建签名:

HMACSHA256( base64UrlEncode(header) + “.” +base64UrlEncode(payload),secret)

签名用于验证消息在此过程中未发生更改。并且,对于使用私钥签名的令牌,它还可以验证 JWT 的发送者是否是它所说的发件人。

将所有内容放在一起,输出是三个 Base64-URL 字符串,由点分隔,可以在 HTML 和 HTTP 环境中轻松传递,同时与基于 XML 的标准(如 SAML)相比更紧凑。

创建项目集成 JWT 实现 token 验证

添加依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.7.5</version>        <relativePath/>     </parent>    <groupId>com.example</groupId>    <artifactId>springboot-jwt-demo</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>springboot-jwt-demo</name>    <description>springboot-jwt-demo</description>    <properties>        <java.version>8</java.version>        <jjwt.version>0.11.2</jjwt.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-configuration-processor</artifactId>            <optional>true</optional>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <optional>true</optional>        </dependency>                <dependency>            <groupId>io.jsonwebtoken</groupId>            <artifactId>jjwt-api</artifactId>            <version>${jjwt.version}</version>        </dependency>        <dependency>            <groupId>io.jsonwebtoken</groupId>            <artifactId>jjwt-impl</artifactId>            <version>${jjwt.version}</version>        </dependency>        <dependency>            <groupId>io.jsonwebtoken</groupId>            <artifactId>jjwt-jackson</artifactId>            <version>${jjwt.version}</version>            <scope>runtime</scope>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>                <configuration>                    <excludes>                        <exclude>                            <groupId>org.projectlombok</groupId>                            <artifactId>lombok</artifactId>                        </exclude>                    </excludes>                </configuration>            </plugin>        </plugins>    </build></project>

添加实体类 LoginUser

import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic class LoginUser {    /**     * 账户     */    private Long accoutNo;    /**     * 用户名     */    private String userName;    /**     * 头像     */    private String headImg;}

新建 JWT 工具类 JWTUtil

import com.example.springbootjwtdemo.model.LoginUser;import io.jsonwebtoken.security.Keys;import lombok.extern.slf4j.Slf4j;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import java.nio.charset.StandardCharsets;import java.security.Key;import java.util.Date;import java.util.Objects;@Slf4jpublic class JWTUtil {    /**     * token 过期时间 正常是7天     */    private static final long EXPIRE = 1000 * 60 * 60 * 24 * 7;    /**     * 加密秘钥 UUID.randomUUID().toString().replaceAll("-","") 生成     */    private static final String SECRET = "ff9b84d01c0940c6a5723a744d0d66a8";    /**     * token前缀     */    private static final String TOKEN_PREFIX = "org";    /**     * subject     */    private static final String SUBJECT = "my";    /**     * 加密秘钥     */    private static final Key key = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));    /**     * 使用系统默认token     *///    private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);    /**     * 生成token     *     * @param loginUser 登录用户     * @return {@link String}     */    public static String geneJsonWebToken(LoginUser loginUser) {        if (Objects.isNull(loginUser)) {            throw new NullPointerException("LoginUser对象不能为空");        }        String token = Jwts.builder().setSubject(SUBJECT)                .claim("accout_no", loginUser.getAccoutNo())                .claim("head_img", loginUser.getHeadImg())                .claim("user_name", loginUser.getUserName())                .setIssuedAt(new Date())                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))                .signWith(key, SignatureAlgorithm.HS256).compact();        token = TOKEN_PREFIX + token;        return token;    }    /**     * 检查 token     *     * @param token 令牌     * @return {@link Claims}     */    public static Claims checkJWT(String token) {        try {            return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();        } catch (Exception e) {            log.info("jwt token解密系统异常:{}", e);            return null;        }    }}

新建控制类 LoginController

import com.example.springbootjwtdemo.model.LoginUser;import com.example.springbootjwtdemo.utils.JWTUtil;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/user")public class LoginController {    @GetMapping("login")    public String login(LoginUser loginUser) {        // 生成token        LoginUser userInfo = new LoginUser();        userInfo.setUserName("展释");        userInfo.setAccoutNo(123213L);        userInfo.setHeadImg("");        return JWTUtil.geneJsonWebToken(loginUser);    }}

请求结果