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