JWT(JSON Web Token) 学习笔记(整合Spring Boot)
1、了解 JWT
1.1、什么是 JWT ?
1.2、什么时候使用 JWT ?
1.3、JWT 的结构?
1.3.1、Header
1.3.2、PayLoad
公共声明:这些可以由使用JWT的人随意定义。但为避免冲突,应在 IANA JSON Web 令牌注册表中定义它们,或将其定义为包含抗冲突命名空间的 URI。
私人声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公开声明。
PayLoad 示例:
{
"iss": "songshu",
"sub": "1234567890",
"name": "haha",
"admin": true
}
然后对 PayLoad 进行 Base64Url 编码,以形成 JSON Web 令牌的第二部分。
警告:
1.3.3、Signature
1.3.4、完整的 JWT
1.4、JSON Web 令牌如何工作?
下图显示了如何获取 JWT 并用于访问 API 或资源:
1.5、为什么要使用 JWT ?
1.5.1、基于传统的 Session 认证
2、JWT 的第一个程序(Spring Boot)
2.1、引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.3</version>
</dependency>
2.2、创建Token并对其进行签名
@SpringBootTest
class JwtApplicationTests {
@Test
void contextLoads() {
//创建Algorithm对象,设置秘钥(“secret”)
Algorithm algorithm = Algorithm.HMAC256("secret");
//创建日期类
Calendar calendar = Calendar.getInstance();
//对当前时间加90秒
calendar.add(Calendar.SECOND,90);
//创建token实例
String token = JWT.create()
.withIssuer("songshu") //设置发行人
.withClaim("username", "lisi") //设置自定义用户名
.withExpiresAt(calendar.getTime()) //设置过期时间
.sign(algorithm); //设置签名,保密,且需要秘钥复杂
System.out.println(token);
}
}
生成 token 结果:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzb25nc2h1IiwiZXhwIjoxNjQ0NDg0MzM2LCJ1c2VybmFtZSI6Imxpc2kifQ.3nqmjgxMMw67jT-KO3T8VC7AQGhI9wU9GucQnAV9NeA
2.3、根据Token解析
@SpringBootTest
class JwtApplicationTests {
@Test
void VerifierTest() {
String token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzb25nc2h1IiwiZXhwIjoxNjQ0NDg0MzM2LCJ1c2VybmFtZSI6Imxpc2kifQ.3nqmjgxMMw67jT-KO3T8VC7AQGhI9wU9GucQnAV9NeA";
//创建Algorithm对象,设置秘钥(“secret”)
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
DecodedJWT jwt = jwtVerifier.verify(token);
System.out.println(jwt.getClaim("username"));
System.out.println(jwt.getIssuer());
}
}
如果令牌具有无效的签名或未满足声明要求,则会引发。JWTVerificationException
3、JWT 使用方法
3.1、Header
Algorithm (“alg”)
返回"Algorithm"值,如果未在标头中定义,则返回 null。
String algorithm = jwt.getAlgorithm();
Type (“typ”)
返回 Type 值,如果未在 Header 中定义,则返回 null。
String type = jwt.getType();
Content Type (“cty”)
返回"Content Type"值,如果未在标头中定义,则返回 null。
String contentType = jwt.getContentType();
Key Id (“kid”)
返回 Key Id 值,如果未在 Header 中定义,则返回 null。
String keyId = jwt.getKeyId();
Private Claims
可以通过调用和传递声明名称来获取令牌标头中定义的其他声明。即使找不到报销申请,也始终会退回报销申请。您可以通过调用 来检查声明的值是否为空。getHeaderClaim()
claim.isNull()
Claim claim = jwt.getHeaderClaim("owner");
使用 创建令牌时,可以通过调用和传递声明映射来指定标头声明。JWT.create()
withHeader()
Map<String, Object> headerClaims = new HashMap();
headerClaims.put("owner", "auth0");
String token = JWT.create()
.withHeader(headerClaims)
.sign(algorithm);
3.2、PayLoad
Issuer (“iss”)
返回"Issuer"值或 null(如果未在有效负载中定义)。
String issuer = jwt.getIssuer();
Subject (“sub”)
返回 Subject 值或 null(如果未在有效负载中定义)。
String subject = jwt.getSubject();
Audience (“aud”)
返回 Audience 值,如果未在有效负载中定义,则返回 null。
List<String> audience = jwt.getAudience();
Expiration Time (“exp”)
返回"Expiration Time"值,如果未在"有效负载"中定义,则返回 null。
Date expiresAt = jwt.getExpiresAt();
Not Before (“nbf”)
返回"Not Before"值,如果未在有效负载中定义,则返回 null。
Date notBefore = jwt.getNotBefore();
Issued At (“iat”)
返回"Issued At"值,如果未在有效负载中定义,则返回 null。
Date issuedAt = jwt.getIssuedAt();
JWT ID (“jti”)
返回 JWT ID 值,如果未在有效负载中定义,则返回 null。
String id = jwt.getId();
Private Claims
令牌的有效负载中定义的其他声明可以通过调用或传递声明名称来获取。即使找不到报销申请,也始终会退回报销申请。您可以通过调用 来检查声明的值是否为空。getClaims()
getClaim()
claim.isNull()
Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name
Claim claim = claims.get("isAdmin");
或
Claim claim = jwt.getClaim("isAdmin");
使用 创建令牌时,可以通过调用并传递名称和值来指定自定义声明。JWT.create()
withClaim()
String token = JWT.create()
.withClaim("name", 123)
.withArrayClaim("array", new Integer[]{1, 2, 3})
.sign(algorithm);
还可以通过调用声明名称的映射并将其传递给值来创建 JWT:withPayload()
Map<String, Object> payloadClaims = new HashMap<>();
payloadClaims.put("@context", "https://auth0.com/");
String token = JWT.create()
.withPayload(payloadClaims)
.sign(algorithm);
还可以通过调用和传递名称和所需值来验证 上的自定义声明。JWT.require()
withClaim()
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("name", 123)
.withArrayClaim("array", 1, 2, 3)
.build();
DecodedJWT jwt = verifier.verify("my.jwt.token");
4、自定义简单的 JWTUtils 类
4.1、创建 JWTUtils 类
public class JWTUtils {
//设置秘钥
private static final String SING = "secret";
/**
* 获取 token
* @param map
* @return
*/
public static String getToken(Map<String,String> map) {
//创建Algorithm对象,设置秘钥(“secret”)
Algorithm algorithm = Algorithm.HMAC256(SING);
//创建日期类
Calendar calendar = Calendar.getInstance();
//对当前时间加90秒
calendar.add(Calendar.DATE,7);
//创建JWT build
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v) -> {
builder.withClaim(k,v);
});
//指定过期时间,并得到token
String token = builder.withExpiresAt(calendar.getTime()).sign(algorithm);
return token;
}
/**
* 验证 token
* @param token
* @return
*/
public static DecodedJWT verify(String token) {
try {
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
return decodedJWT;
} catch (JWTVerificationException exception){
//Invalid signature/claims
throw exception;
}
}
}
4.2、测试
@Test
void JWTUtilsTest() {
HashMap<String,String> map = new HashMap<>();
map.put("username","songshu");
//获取 token
String token = JWTUtils.getToken(map);
System.out.println(token);
//验证
DecodedJWT verify = JWTUtils.verify(token);
if (verify != null) {
String username = verify.getClaim("username").asString();
System.out.println(username);
}
}
5、SpringBoot 整合 JWT
5.1、准备工作
5.1.1、引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
5.1.2、创建数据库表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
5.1.3、配置文件
server.port=8081
#mysql配置
spring.datasource.url=jdbc:mysql://localhost:3306/school?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
5.2、编写代码
5.2.1、创建 User 类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private String id;
private String name;
private String password;
}
5.2.2、创建 UserMapper 接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
启动类上配置包扫描
@SpringBootApplication
@MapperScan("com.songshu.jwt.mapper")
public class JwtApplication {
public static void main(String[] args) {
SpringApplication.run(JwtApplication.class, args);
}
}
5.2.3、创建 UserService 接口和 UserServiceImpl 类
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User login(User user) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",user.getUsername());
queryWrapper.eq("password",user.getPassword());
//根据用户名和密码进行验证
User user1 = userMapper.selectOne(queryWrapper);
if (user1 != null) {
return user1;
}
throw new RuntimeException("账号或密码不存在");
}
}
5.2.4、创建 UserController 类
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 登录
* @param user
* @return
*/
@PostMapping("/login")
public Map<String,Object> login(User user) {
Map<String,Object> map = new HashMap<>();
try {
Map<String,String> payLoad = new HashMap<>();
User user1 = userService.login(user);
payLoad.put("id",user1.getId().toString());
payLoad.put("username",user1.getUsername());
//生成token
String token = JWTUtils.getToken(payLoad);
map.put("state",true);
map.put("msg","登录成功");
map.put("token",token);
return map;
} catch (Exception e) {
map.put("state",false);
map.put("msg",e.getMessage());
}
return map;
}
/**
* 测试 token
* @param token
* @return
*/
@PostMapping("/testToken")
public Map<String,Object> testToken(String token) {
Map<String,Object> map = new HashMap<>();
try {
DecodedJWT decodedJWT = JWTUtils.verify(token);
map.put("state",true);
map.put("msg","令牌认证成功");
return map;
} catch (SignatureVerificationException e) {
//签名验证异常
map.put("msg","签名验证异常");
} catch (TokenExpiredException e) {
//token过期异常
map.put("msg","token过期异常");
} catch (AlgorithmMismatchException e) {
//算法不一致异常
map.put("msg","算法不一致异常");
} catch (Exception e) {
map.put("msg","签名验证异常");
}
map.put("state",false);
return map;
}
}
5.3、通过 Postman 进行测试
5.4、拦截器
5.4.1、创建 JWTInterceptor 拦截器
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String,Object> map = new HashMap<>();
//JWT官网建议把 token 放入 header 中
//从请求头中获取 token
String token = request.getHeader("token");
try {
DecodedJWT decodedJWT = JWTUtils.verify(token);
return true;
} catch (SignatureVerificationException e) {
//签名验证异常
e.printStackTrace();
map.put("msg","签名验证异常");
} catch (TokenExpiredException e) {
//token过期异常
e.printStackTrace();
map.put("msg","token过期异常");
} catch (AlgorithmMismatchException e) {
//算法不一致异常
e.printStackTrace();
map.put("msg","算法不一致异常");
} catch (Exception e) {
e.printStackTrace();
map.put("msg","签名验证异常");
}
map.put("state",false);
//将Map转化为Json
String data = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(data);
return false;
}
}
5.4.2、创建 InterceptorConfig 配置类,并且配置拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/testToken")
.excludePathPatterns("/user/login");
}
}
5.4.3、修改 UserController 类
/**
* 测试 token
* @return
*/
@PostMapping("/testToken")
public Map<String,Object> testToken() {
Map<String,Object> map = new HashMap<>();
//处理业务逻辑
map.put("state",true);
map.put("msg","令牌认证成功");
return map;
}
5.4.4、测试
与上面相同。