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); }}