目录
JWT介绍
JWT全称为JSON Web Token,是目前最流行的跨域身份验证解决方案。JWT是为了在网络应用环境间传递声明而制定的一种基于JSON的开放标准,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可被加密 。
JWT的数据结构
JWT其实就是一个很长的字符串,字符之间通过"."分隔符分为三个子串,各字串之间没有换行符。每一个子串表示了一个功能块,总共有三个部分:JWT头(header)、有效载荷(payload)、签名(signature)
JWT头
JWT头是一个描述JWT元数据的JSON对象,通常如下所示:
{"alg": "HS256","typ": "JWT"}
- alg:表示签名使用的算法,默认为HMAC SHA256(写为HS256)
- typ:表示令牌的类型,JWT令牌统一写为JWT
最后,使用Base64 URL算法将上述JSON对象转换为字符串
有效载荷
有效载荷,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。
有效载荷部分规定有如下七个默认字段供选择:
- iss:发行人
- exp:到期时间
- sub:主题
- aud:用户
- nbf:在此之前不可用
- iat:发布时间
- jti:JWT ID用于标识该JWT
除以上默认字段外,还可以自定义私有字段。
最后,同样使用Base64 URL算法将有效载荷部分JSON对象转换为字符串
签名
签名实际上是一个加密的过程,是对上面两部分数据通过指定的算法生成哈希,以确保数据不会被篡改。
首先需要指定一个密码(secret),该密码仅仅保存在服务器中,并且不能向用户公开。然后使用JWT头中指定的签名算法(默认情况下为HMAC SHA256)生成签名哈希
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
JWT签名算法
JWT签名算法中,一般有两个选择:HS256和RS256。
HS256 (带有 SHA-256 的 HMAC )是一种对称加密算法, 双方之间仅共享一个密钥。由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密。
RS256 (采用SHA-256 的 RSA 签名) 是一种非对称加密算法, 它使用公共/私钥对: JWT的提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。
jjwt介绍
jjwt是一个提供JWT创建和验证的Java库。
jjwt的maven坐标:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
入门案例
创建maven工程jwt_demo并配置pom.xml文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>jwt_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
</project>
编写单元测试
package com.text;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.*;
import org.junit.Test;
import java.io.DataInputStream;
import java.io.InputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* JWT签名算法中,一般有两个选择:HS256,RS256
*/
public class test {
// 签名算法的秘钥
private final String SECRET_KEY = "test";
/**
* 通过jjwt生成和解析jwt令牌,不使用签名算法
*/
@Test
public void test1() {
//用于封装jwt的header部分
Map header = new HashMap();
header.put("alg",SignatureAlgorithm.NONE.getValue());//不使用签名算法
header.put("typ","JWT");//指定令牌的类型,如果是jwt令牌统一都写为JWT
//用于封装jwt的body部分
Map body = body();
//使用jjwt提供的API生成jwt令牌
String jwt = Jwts.builder()
.setHeader(header)//jwt的头部
.setClaims(body)//jwt的有效载荷
.setId(UUID.randomUUID().toString())//当前jwt的唯一标识
.compact();
System.out.println("jwt令牌:\n" + jwt);
//解析jwt令牌
analysis(jwt,null,null);
}
/**
* 通过jjwt生成和解析jwt令牌,使用HS256签名算法
*/
@Test
public void hS256Test() {
//用于封装jwt的header部分
Map header = new HashMap();
header.put("alg", SignatureAlgorithm.HS256.getValue());//不使用签名算法
header.put("typ","JWT");//指定令牌的类型,如果是jwt令牌统一都写为JWT
//用于封装jwt的body部分
Map body = body();
//使用jjwt提供的API生成jwt令牌
String jwt = Jwts.builder()
.setHeader(header)//jwt的头部
.setClaims(body)//jwt的有效载荷
//jwt的签名
.signWith(SignatureAlgorithm.HS256,SECRET_KEY)//参数1:使用的签名方式 参数2:秘钥
.setId(UUID.randomUUID().toString())//当前jwt的唯一标识
.compact();
System.out.println("jwt令牌:\n" + jwt);
//解析jwt令牌
analysis(jwt,SECRET_KEY,null);
}
/**
* 生成自己的 秘钥/公钥 对
*/
@Test
public void createRS256SecretKeys() throws Exception{
//使用RSA算法生成公钥和私钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
//根据SECRET_KEY生成一个随机数
SecureRandom secureRandom = new SecureRandom(SECRET_KEY.getBytes());
keyPairGenerator.initialize(1024, secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 使用keyPair生成一对公钥和私钥
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();//公钥
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();//私钥
// 将公钥和私钥写入到文件中
FileUtil.writeBytes(publicKeyBytes,"D:\\pub.key");//将生成的文件复制到resource目录中
FileUtil.writeBytes(privateKeyBytes,"D:\\pri.key");//将生成的文件复制到resource目录中
}
/**
* 通过jjwt生成和解析jwt令牌,使用RS256签名算法
*/
@Test
public void rS256Test() throws Exception {
//用于封装jwt的header部分
Map header = new HashMap();
header.put("alg", SignatureAlgorithm.RS256.getValue());//使用RS256签名算法
header.put("typ","JWT");//指定令牌的类型,如果是jwt令牌统一都写为JWT
//用于封装jwt的body部分
Map body = body();
//私钥,根据指定的私钥文件生成jwt令牌时需要使用私钥 参数:文件路径
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("pri.key");
DataInputStream dis = new DataInputStream(resourceAsStream);
byte[] keyBytes = new byte[resourceAsStream.available()];
dis.readFully(keyBytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);
//使用jjwt提供的API生成jwt令牌
String jwt = Jwts.builder()
.setHeader(header)//jwt的头部
.setClaims(body)//jwt的有效载荷
//jwt的签名
.signWith(SignatureAlgorithm.RS256,privateKey)//参数1:使用的签名方式 参数2:秘钥
.setId(UUID.randomUUID().toString())//当前jwt的唯一标识
.compact();
System.out.println("jwt令牌:\n" + jwt);
//公钥,根据指定的公钥文件生成jwt令牌时需要使用公钥 参数:文件路径
resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("pub.key");
dis = new DataInputStream(resourceAsStream);
keyBytes = new byte[resourceAsStream.available()];
dis.readFully(keyBytes);
X509EncodedKeySpec spec2 = new X509EncodedKeySpec(keyBytes);
kf = KeyFactory.getInstance("RSA");
PublicKey publicKey = kf.generatePublic(spec2);
//解析jwt令牌
analysis(jwt,null,publicKey);
}
/**
* 解析jwt令牌
* @param jwt jwt字符串
* @param key 解析的秘钥
* @param publicKey 解析的公钥
*/
public void analysis(String jwt,String key,PublicKey publicKey) {
//判断是否存在 isBlank的判断情况:1.null=true 2.""=true 3."\n\t"=true 4."abc"=false
if (StrUtil.isBlank(jwt)) {
return;
}
Jwt result = null;
if (StrUtil.isBlank(key) && publicKey == null) {
result = Jwts.parser().parse(jwt);
}
if (StrUtil.isNotBlank(key)) {
result = Jwts.parser().setSigningKey(key).parse(jwt);
}
if (publicKey != null) {
result = Jwts.parser().setSigningKey(publicKey).parse(jwt);
}
System.out.println("解析jwt后的头部:\n" + result.getHeader());
System.out.println("解析jwt后的有效载荷:\n" + result.getBody());
}
/**
* 模拟数据库中的数据
* @return 返回用户信息
*/
public Map body() {
Map body = new HashMap();
body.put("Id",1);
body.put("name","张三");
body.put("accont","2454076988");
body.put("password","123456");
body.put("stater",1);
return body;
}
}
JWT的基本使用案例
入门案例
创建spring-boot工程jwtdemo并配置pom.xml文件
<?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.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>jwtdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jwtdemo</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
</dependencies>
</project>
创建实体类User
@Data
public class User {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private String accont;
private String password;
private Integer stater;
}
创建结果类
package com.example.comon;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private Object data;
private String msg;
public static <T> Result<T> success(T object) {
Result result = new Result();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> success(T object,String jwt) {
Result result = new Result();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
}
创建UserController
package com.example.controller;
import com.example.comon.Result;
import com.example.entity.User;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/login")
public Result<Map> login(HttpServletRequest request, HttpServletResponse response,User user) {
log.info("user:{}",user);
return userService.login(request, response, user);
}
@PostMapping
public Result<Map> test(HttpServletRequest request, HttpServletResponse response,User user) {
return userService.testsave(request,response,user);
}
}
创建JwtUtil工具类
package com.example.util;
import cn.hutool.core.util.StrUtil;
import com.example.entity.User;
import com.example.exception.NotTokenException;
import com.example.exception.NullTokenException;
import com.example.exception.SignatureVerificationException;
import io.jsonwebtoken.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
public class JwtUtil {
// token时效:24小时
public static final long EXPIRE = 1000 * 60 * 60 * 24;
// 签名哈希的密钥,对于不同的加密算法来说含义不同
public static final String SECRET_KEY = "test";
/**
* 根据用户id和昵称生成token
*/
public String getJwtToken(User user){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", SignatureAlgorithm.HS256.getValue())
.claim("id",user.getId())
.claim("name",user.getName())
.claim("accont",user.getAccont())
.claim("password",user.getPassword())
.claim("stater",user.getStater())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
*/
public boolean checkToken(HttpServletRequest request) {
// 从http请求头中获取token字符串
String token = request.getHeader("token");
String jwt = request.getSession().getAttribute("token").toString();
//这里只是自己定义的一些异常类(可以不写)
Jwt parse = Jwts.parser().setSigningKey(SECRET_KEY).parse(token);
String type = parse.getHeader().getType();
if (!type.equals("JWT")) {
throw new NotTokenException();//非JWT类型
}
if(StrUtil.isEmpty(token)) {
throw new NullTokenException();//请求头中token为空
}
if (!token.equals(jwt)) {
throw new SignatureVerificationException();//请求头中的token与服务器中不匹配
}
return true;
}
/**
* 根据token解析出jwt对象
*/
public Jwt getJwt(String jwtToken) {
return Jwts.parser().setSigningKey(SECRET_KEY).parse(jwtToken);
}
}
创建UserMapper,UserService
略
创建UserServiceImpl
package com.example.service.impl;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.comon.Result;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import com.example.util.JwtUtil;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public Result<Map> login(HttpServletRequest request, HttpServletResponse response, User user) {
//判断用户是否存在
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getAccont,user.getAccont());
User user1 = getOne(lambdaQueryWrapper);
if (!user.getPassword().equals(user1.getPassword())) {
return Result.error("登录失败");
}
if (user1.getStater() != 1) {
return Result.error("无权限");
}
//生成jwt令牌
String jwt = new JwtUtil().getJwtToken(user1);
request.getSession().setAttribute("token",jwt);
//返回信息
Map map = new HashMap();
map.put("msg","登录成功");
map.put("token",jwt);
return Result.success(map,"登录成功");
}
@Override
public Result<Map> testsave(HttpServletRequest request, HttpServletResponse response, User user) {
String token = request.getSession().getAttribute("token").toString();
Jwt jwt = new JwtUtil().getJwt(token);
String body = jwt.getBody().toString();
body=body.replace("=",":");
User user1 = JSONUtil.toBean(body, User.class);
Map map = new HashMap();
map.put("操作人",user1.getName());
map.put("操作账号",user1.getAccont());
return Result.success(map,"操作成功");
}
}
创建JwtInterceptor拦截器
package com.example.interceptor;
import com.example.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("拦截到了{}:{}请求,且jwt:{}",request.getMethod(),request.getRequestURI());
// 1.校验JWT字符串
return new JwtUtil().checkToken(request);
}
}
创建WebConfig拦截器配置类
package com.example.config;
import com.example.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor()).excludePathPatterns(
"/user/login"
);
}
}
测试
登录
签名不一致时
签名一直时