springboot 集成JWT实现token验证

时间:2022-12-01 11:56:03

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 验证

添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<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/> <!-- lookup parent from repository -->
    </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>

        <!-- JWT相关 -->
        <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
@NoArgsConstructor
public 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;

@Slf4j
public 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);

    }
}

请求结果

springboot 集成JWT实现token验证