自学内容网 自学内容网

web开发工具之:三、JWT的理论知识,java的支持,封装的工具类可以直接使用

前言

本文详细介绍了在Java Spring Boot中实现JWT(JSON Web Token)的完整流程。通过依赖io.jsonwebtoken的jjwt库,我们可以方便地生成、解析和验证JWT。文章提供了配置文件、工具类和初始化配置的详细代码,涵盖了JWT的生成、过期时间设置、刷新机制以及从token中提取信息等功能。这些方法在用户认证、授权和单点登录等场景中具有重要应用,有助于提高Web应用的安全性和效率。

一、JWT的理论知识

1. 什么是 JWT(JSON Web Token)?

JWT(JSON Web Token)是一种基于 JSON 的开放标准 (RFC 7519) ,用于在各方之间作为紧凑且安全的方式传输信息。它常用于认证和授权场景,尤其是在 Web 开发中非常流行。

2. JWT 的组成

一个 JWT 通常由三部分组成,每部分通过 . 分隔:

  1. Header(头部)

    • 描述令牌的元信息,比如使用的签名算法。
    • 通常是一个简单的 JSON 对象,如:
      {
        "alg": "HS256",
        "typ": "JWT"
      }
      
    • alg 指定签名算法(如 HS256、RS256),typ 表示令牌的类型(一般为 “JWT”)。
  2. Payload(有效载荷)

    • 包含实际要传递的数据,例如用户信息或声明(claims)。
    • 这是一个 JSON 对象,可以包括:
      • 标准声明(预定义字段,如用户 ID、过期时间等)。
      • 自定义声明(开发者自定义的数据)。
    • 示例:
      {
        "sub": "1234567890",
        "name": "John Doe",
        "admin": true,
        "exp": 1672444800
      }
      
    • 注意:Payload 是可以被 解码和阅读 的,因此不要存储敏感信息。
  3. Signature(签名)

    • 用于验证 JWT 的完整性,确保它未被篡改。
    • 签名是通过以下方式生成的:
      HMACSHA256(
          base64UrlEncode(header) + "." + base64UrlEncode(payload),
          secret
      )
      
    • 如果签名验证失败,JWT 就会被判定为无效。

3. JWT 的特点

  1. 紧凑性

    • JWT 使用 Base64Url 编码,令牌是一个字符串,适合通过 URL 或 HTTP 请求头传输。
  2. 自包含性

    • JWT 内部可以包含必要的用户信息,减少了对服务器多次查询的需求。
  3. 无状态

    • JWT 是一种无状态认证方式,服务器不需要存储用户会话数据。
  4. 安全性

    • 签名确保令牌未被篡改。
    • 但注意:Payload 可以被解码,因此敏感信息需要加密或避免存储在 JWT 中。

4. JWT 的使用场景

  1. 用户认证

    • 用户登录成功后,服务器生成一个 JWT,返回给客户端。
    • 客户端每次请求时将 JWT 附带在请求头(通常是 Authorization: Bearer <token>)中。
    • 服务器通过验证 JWT 来确定用户身份。
  2. 授权

    • 用于控制用户对资源的访问权限。
    • JWT 中可以包含用户角色或权限信息。
  3. 单点登录(SSO)

    • JWT 是 SSO 的一种理想选择,因其跨平台的特点,可以在不同的服务间传递用户身份。

5. JWT 的生命周期

  • 生成

    1. 用户向服务器发送登录请求。
    2. 服务器验证用户身份后生成 JWT,通常会设置过期时间(如 15 分钟)。
    3. JWT 返回给客户端。
  • 验证

    1. 客户端在后续请求中发送 JWT。
    2. 服务器通过签名验证 JWT 是否有效。
  • 刷新

    • JWT 的有效期一般较短,若用户需要长期登录,可以结合 Refresh Token 实现令牌刷新。

6. JWT 的优点

  1. 无状态性:服务器无需存储会话数据。
  2. 高效性:可以通过单个令牌传递多种信息。
  3. 跨语言支持:JWT 是一种标准,可以被多种编程语言支持。

7. JWT 的注意事项

  1. 不要存储敏感数据

    • Payload 是明文可解码的,敏感信息应避免放入 JWT。
  2. 使用 HTTPS

    • 确保 JWT 在传输过程中被加密,防止被中间人攻击。
  3. 设置有效期

    • 短生命周期的 JWT 可以减少被盗用的风险。
  4. 签名密钥管理

    • 确保签名密钥的安全,避免泄漏。

5. JWT 示例

一个完整的 JWT 示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

解码后:

  1. Header

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  2. Payload

    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
    
  3. Signature

    SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    

总结

JWT 是一种高效的认证和授权工具,适合现代 Web 应用。学习 JWT 后,可以轻松实现用户登录、权限控制等功能,为 Web 开发打下坚实基础!

二、java的springboot支持

下面有3个类,这三个类都在同一个包里,构成了一个工具类。可以通过 JwtTokenUtil.java 类直接使用封装好的方法即可。

1. pom依赖

先直接上pom依赖

<!--JWT-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

从Maven的依赖官网搜索了一下,从截图中可以看到有以下四个。

jjwt 是一个包含多个依赖的聚合包,它包括了 jjwt-api、jjwt-impl 和 jjwt-jackson(或 jjwt-gson,取决于你选择的 JSON 处理库)。

  • jjwt-api 提供了 JWT 的基本接口和抽象类。
  • jjwt-impl 则提供了 JWT 的具体实现,它是运行时依赖。
  • jjwt-jackson(或 jjwt-gson)用于 JSON 处理,也是运行时依赖。

因此,当你导入 jjwt 依赖时,实际上是在项目中引入了这三个组件。这样做可以简化依赖管理,因为只需要引入一个依赖即可。

在这里插入图片描述

2. application.yaml配置

#JWT  自定义属性
jwt:
#  secretKey: 78944878877848fg)   # 秘钥
  secretKey: fengfanli            # 秘钥
  accessTokenExpireTime: PT2H     # 过期时间 两个小时
  refreshTokenExpireTime: PT8H    # 刷新token,
  refreshTokenExpireAppTime: P30D
  issuer: fengfanli.com           # 签名:

3. 加载application.yaml配置的配置类

package com.feng.companyframe.jwt;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * @ClassName: PropertityConfig
 * @Description: token 读取的配置属性
 * @createTime: 2020/2/4 11:04
 * @Author: 冯凡利
 * @UpdateUser: 冯凡利
 * @Version: 0.0.1
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtPropertiesConfig {
    private String secretKey;
    private Duration accessTokenExpireTime;  // java.time.Duration;
    private Duration refreshTokenExpireTime;
    private Duration refreshTokenExpireAppTime;
    private String issuer;
}

4. jwt工具类:JwtTokenUtil.java

package com.feng.companyframe.jwt;

import com.feng.companyframe.constant.Constant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.thymeleaf.util.StringUtils;

import javax.xml.bind.DatatypeConverter;
import java.time.Duration;
import java.util.Date;
import java.util.Map;

/**
 * @ClassName: JwtTokenUtil
 * @Description: JWT token 工具类
 * @createTime: 2020/2/3 23:24
 * @Author: 冯凡利
 * @UpdateUser: 冯凡利
 * @Version: 0.0.1
 */
@Slf4j
public class JwtTokenUtil {
    private static String secretKey;
    private static Duration accessTokenExpireTime;  // java.time.Duration;
    private static Duration refreshTokenExpireTime;
    private static Duration refreshTokenExpireAppTime;
    private static String issuer;

    /**
     *  读取配置文件类中属性,放到本文件中
     * @param jwtPropertiesConfig
     */
    public static void setTokenProperties(JwtPropertiesConfig jwtPropertiesConfig) {
        secretKey = jwtPropertiesConfig.getSecretKey();
        accessTokenExpireTime = jwtPropertiesConfig.getAccessTokenExpireTime();
        refreshTokenExpireTime = jwtPropertiesConfig.getRefreshTokenExpireTime();
        refreshTokenExpireAppTime = jwtPropertiesConfig.getRefreshTokenExpireAppTime();
        issuer = jwtPropertiesConfig.getIssuer();
    }

    /**
     * 生成 access_token
     *
     * @param subject 主题
     * @param claims  存储在JWT里面的信息 一般放些用户的权限/角色信息
     * @return
     */
    public static String getAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(issuer, subject, claims, accessTokenExpireTime.toMillis(), secretKey);
    }

    /**
     * 生产 PC refresh_token(PC 端过期时间短一些)
     *
     * @param subject
     * @param claims
     * @return
     */
    public static String getRefreshToken(String subject, Map<String, Object> claims) {
        return generateToken(issuer, subject, claims, refreshTokenExpireTime.toMillis(), secretKey);
    }

    /**
     * 生产 App端 refresh_token
     *
     * @param subject
     * @param claims
     * @return
     */
    public static String getRefreshAppToken(String subject, Map<String, Object> claims) {
        return generateToken(issuer, subject, claims, refreshTokenExpireAppTime.toMillis(), secretKey);
    }

    /**
     * 生成token, 以上三个方法 都调用此方法
     *
     * @param issuer    签发人
     * @param subject   主题:令牌的主题,通常是用户信息或令牌的主要用途。一般可以使用用户ID作为主题
     * @param claims    存储在JWT里面的信息 一般放些用户的权限/角色信息
     * @param ttlMillis 有效时间(毫秒) --》配置文件获取
     * @param secret    秘钥  --》配置文件获取
     * @return 返回 token java.lang.String
     */
    public static String generateToken(String issuer, String subject, Map<String, Object> claims,
                                       long ttlMillis, String secret) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        long nowMills = System.currentTimeMillis();
        Date now = new Date(nowMills);

        // 对秘钥进行编码 成数组
        byte[] signingKey = DatatypeConverter.parseBase64Binary(secret);

        // 创建 jwt 构造器
        JwtBuilder builder = Jwts.builder();
        if (null != claims) {
            builder.setClaims(claims);
        }
        if (!StringUtils.isEmpty(subject)) {
            builder.setSubject(subject);
        }
        if (!StringUtils.isEmpty(issuer)) {
            builder.setIssuer(issuer);
        }
        // 主题在 此时 进行时间设定
        builder.setIssuedAt(now);
        if (nowMills >= 0) {
            long expMillis = nowMills + ttlMillis;
            Date exp = new Date(expMillis);
            // 设置过期时间
            builder.setExpiration(exp);
        }
        // 拿着 算法和 秘钥进行 签名
        builder.signWith(signatureAlgorithm, signingKey);
        // 返回 jwt
        return builder.compact();
    }

// 以下为 对token 的操作静态方法

    /**
     * 从令牌中获取 数据声明 Claims
     *
     * @param token 传入的 jwt
     * @return io.jsonwebtoken.Claims
     */
    public static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(secretKey))
                    .parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 获取用户id
     *
     * @param token
     * @return io.jsonwebtoken.Claims
     */
    public static String getUserId(String token) {
        String userId = null;
        try {
            Claims claims = getClaimsFromToken(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            log.error("eror={}", e.getLocalizedMessage());
        }
        return userId;
    }

    /**
     * 获取用户名
     *
     * @param token
     * @return username String
     */
    public static String getUserName(String token) {
        String username = null;
        try {
            Claims claims = getClaimsFromToken(token);
            username = (String) claims.get(Constant.JWT_USER_NAME);
        } catch (Exception e) {
            log.error("eror={}", e);
        }
        return username;
    }

    /**
     * 验证token 是否过期(true:已过期 false:未过期)
     *
     * @param token
     * @return java.lang.Boolean
     */
    public static Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            // token的过期时间 与 当前时间比较,如果小于,则为 true,为过期
            return expiration.before(new Date());
        } catch (Exception e) {
            log.error("error={}", e.getLocalizedMessage());
            return true;
        }
    }

    /**
     * 校验令牌(true:验证通过 false:验证失败)
     *
     * @param token
     * @return Boolean
     */
    public static Boolean validateToken(String token) {
        Claims claimsFromToken = getClaimsFromToken(token);
        //
        return (null != claimsFromToken && !isTokenExpired(token)); //  && :and 都为true
    }

    /**
     * 刷新token
     *
     * @param refreshToken
     * @param claims       主动去刷新的时候 改变JWT payload 内的信息
     * @return String
     */
    public static String refreshToken(String refreshToken, Map<String, Object> claims) {
        String refreshedToken;
        try {
            Claims parserclaims = getClaimsFromToken(refreshToken);
            /**
             * 刷新token的时候如果为空说明原先的 用户信息不变 所以就引用上个token里的内容
             */
            if (null == claims) {
                claims = parserclaims;
            }
            refreshedToken = generateToken(parserclaims.getIssuer(),
                    parserclaims.getSubject(), claims, accessTokenExpireTime.toMillis(), secretKey);
        } catch (Exception e) {
            refreshedToken = null;
            log.error("error={}", e.getLocalizedMessage());
        }
        return refreshedToken;
    }


    /**
     * 获取token的剩余过期时间
     *
     * @param token
     * @return
     */
    public static long getRemainingTime(String token){
        long result=0;
        try {
            long nowMillis = System.currentTimeMillis();
            result= getClaimsFromToken(token).getExpiration().getTime()-nowMillis;
        } catch (Exception e) {
            log.error("error={}",e.getLocalizedMessage());
        }
        return result;
    }

}

生成 accessToken 的核心函数是:generateToken(String issuer, String subject, Map<String, Object> claims,long ttlMillis, String secret),简单介绍一下:

这段代码使用 JJWT 库生成 JSON Web Token (JWT),用于身份验证和信息传递。以下是简要说明:

  1. 功能:生成一个带签名的 JWT,包括发布者、主题、自定义数据(claims)、过期时间和签名。

  2. 主要步骤

    • 指定签名算法(HS256)。
    • 使用 Base64 解码密钥。
    • 设置令牌信息(如发布者、主题、自定义声明、签发时间等)。
    • 如果指定了有效期,计算过期时间并设置。
    • 使用密钥和算法对令牌进行签名。
    • 返回最终的 JWT 字符串。
  3. 主要用途

    • 身份认证:生成令牌用于验证用户身份。
    • 数据传递:通过 claims 携带额外信息。
    • 过期控制:ttlMillis 设置令牌有效期。

这是生成 JWT 的基础方法,适用于各种 Web 开发场景,灵活且易用。

5. InitializerJwtPropertiesConfig.java:将配置类注入到工具类中

package com.feng.companyframe.jwt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName: InitializerJwtPropertiesConfig
 * @Description: 将yml配置文件中的数据注入到 JwtTokenUtil 工具类中
 * @createTime: 2020/2/5 10:42
 * @Author: 冯凡利
 * @UpdateUser: 冯凡利
 * @Version: 0.0.1
 */
@Configuration
public class InitializerJwtPropertiesConfig {

//    @Autowired
    private JwtPropertiesConfig jwtPropertiesConfig;

    public InitializerJwtPropertiesConfig(JwtPropertiesConfig jwtPropertiesConfig) {
        JwtTokenUtil.setTokenProperties(jwtPropertiesConfig);
    }
}


三、总结

JwtTokenUtil.java 从该类中可以看出:

  1. 配置文件中配置了秘钥、token过期时间、刷新token过期时间、APP刷新token过期时间(这是APP端的)、签名。这是SHA-256算法的一些配置。
  2. getAccessToken(String subject, Map<String, Object> claims)函数根据 主题和主体(放到jwt的载体中) 生成token。
  3. 其他函数:getClaimsFromToken()等,都是从token中获取claims主体的信息。claims是一个map,可以放一些用户常用信息的,一般放置:用户名称、用户的角色、用户的权限 等。

原文地址:https://blog.csdn.net/qq_40036754/article/details/145216112

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!