如果大家觉得有用的话,可以关注我下面的微信公众号,极客李华,我会在里面更新更多行业资讯,企业面试内容,编程资源,如何写出可以让大厂面试官眼前一亮的简历等内容,让大家更好学习编程,我的抖音,B站也叫极客李华。大家喜欢也可以关注一下
在当今的互联网时代,身份验证和授权是保护应用程序和保护用户数据的关键。而 JSON Web Token (简称 JWT)是一种用于身份验证和授权的开放标准,广泛应用于web应用程序和API中。本文将深入介绍 JWT,包括其组成、工作原理以及常见的应用场景。
1. 什么是 JSON Web Token (JWT)? JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式来在各方之间安全地传输信息。它是一个基于 JSON 格式的令牌,由三个部分组成:头部(Header)、载荷(Payload)、签名(Signature)。其中,每一部分都使用 Base64 编码,形成一个使用点进行分隔的字符串。
2. JWT 的组成
3. JWT 的工作原理 JWT 工作原理如下:
4. JWT 的应用场景 JWT 是一种灵活而强大的工具,可用于多种应用场景,包括:
# 1.引入依赖
<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
# 2.生成token
// 创建一个 Calendar 实例,用于设置过期时间
Calendar instance = Calendar.getInstance();
// 在当前时间的基础上增加90秒
instance.add(Calendar.SECOND, 90);
// 生成令牌
String token = JWT.create()
.withClaim("username", "张三") // 设置自定义用户名
.withExpiresAt(instance.getTime()) // 设置过期时间为 Calendar 实例的时间
.sign(Algorithm.HMAC256("token!Q2W#E$RW")); // 使用 HMAC256 签名算法和密钥进行签名
// 输出令牌
System.out.println(token);
注释解释:
Calendar
实例,表示当前时间,该实例用于设置过期时间。
JWT.create()
方法创建一个 JWT 实例,用于生成令牌。
withClaim("username", "张三")
方法设置自定义声明,在这里设置了用户名。
withExpiresAt(instance.getTime())
方法设置过期时间,将 instance
实例的时间设置为令牌的过期时间。
token
变量中。
System.out.println(token)
将令牌内容输出到控制台。
- 生成结果
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5byg5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg
# 3.根据令牌和签名解析数据
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户名: " + decodedJWT.getClaim("username").asString());
System.out.println("过期时间: "+decodedJWT.getExpiresAt());
## 常见异常信息
- SignatureVerificationException: 签名不一致异常
- TokenExpiredException: 令牌过期异常
- AlgorithmMismatchException: 算法不匹配异常
- InvalidClaimException: 失效的payload异常
/**
* JWTUtils 类用于生成和验证 JWT 令牌,以及获取令牌中的 payload。
*/
public class JWTUtils {
private static String TOKEN = "token!Q@W3e4r"; // 定义密钥
/**
* 生成 JWT 令牌
* @param map 传入的 Payload 数据
* @return 返回生成的令牌
*/
public static String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
// 遍历传入的 Payload 数据,并添加到 Builder 中
map.forEach((k,v)->{
builder.withClaim(k,v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,7);
// 设置过期时间为 7 秒后
builder.withExpiresAt(instance.getTime());
// 使用 HMAC256 签名算法进行签名,并返回令牌字符串
return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}
/**
* 验证 JWT 令牌
* @param token 要验证的令牌字符串
*/
public static void verify(String token){
// 创建一个 JWTVerifier 实例,使用相同的密钥构建
JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
/**
* 获取令牌中的 Payload 数据
* @param token 要解析的令牌字符串
* @return 解码后的令牌对象(DecodedJWT)
*/
public static DecodedJWT getToken(String token){
// 创建一个 JWTVerifier 实例,使用相同的密钥构建,并对令牌进行验证和解码
return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
}
注释解释:
TOKEN
是用来生成和验证 JWT 令牌所使用的密钥。getToken()
方法用于生成 JWT 令牌,接收一个 Map
类型的参数作为 Payload 数据,并返回生成的令牌字符串。verify()
方法用于验证 JWT 令牌,接收令牌字符串作为参数。创建一个 JWTVerifier 实例,使用相同的密钥进行构建,并对令牌进行验证。getToken()
方法用于获取令牌中的 Payload 数据。接收要解析的令牌字符串作为参数。创建一个 JWTVerifier 实例,使用相同的密钥构建,并对令牌进行验证和解码。返回解码后的令牌对象(DecodedJWT)。 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!--引入mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--引入druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
<!--引入mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(80) DEFAULT NULL COMMENT '用户名',
`password` varchar(40) DEFAULT NULL COMMENT '用户密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
/**
* <p>
*
* </p>
*
* @author jakelihua
* @since 2023-08-14
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 用户名
*/
private String name;
/**
* 用户密码
*/
private String password;
}
/**
* InterceptorConfig 是一个配置类,用于添加拦截器。
* 在这个类中,我们可以配置需要拦截的接口路径以及排除不需要拦截的接口路径。
* 在这个例子中,我们添加了JWTInterceptor拦截器来对请求进行token验证,
* 并设置了"/user/test"接口需要进行验证,而"/user/login"接口则被排除在验证之外,即所有用户都放行登录接口。
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 添加拦截器配置
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/test") // 对"/user/test"接口进行token验证
.excludePathPatterns("/user/login"); // 所有用户都放行登录接口
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
public Result(T data) {
this.code = 200;
this.message = "success";
this.data = data;
}
public Result(T data, boolean success, String message) {
if (success) {
this.code = 200;
this.message = "success";
} else {
this.code = 500; // 自定义错误状态码(示例为500)
this.message = message;
}
this.data = data;
}
public Result(int code, String message) {
this.code = code;
this.message = message;
this.data = null;
}
/**
* 返回执行失败的结果(默认状态码为500)
*
* @param message 提示信息
* @return 失败的结果对象
*/
public static <T> Result<T> fail(String message) {
return new Result<>(500, message);
}
/**
* 返回执行失败的结果(自定义状态码和提示信息)
*
* @param code 状态码
* @param message 提示信息
* @return 失败的结果对象
*/
public static <T> Result<T> fail(int code, String message) {
return new Result<>(code, message);
}
}
public class JWTUtils {
private static final String SING = "!Q@W3e4r%T^Y";
/**
* 生成token header.payload.sing
*/
public static String getToken(Map<String,String> map){
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7);//默认7天过期
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime())//指定令牌过期时间
.sign(Algorithm.HMAC256(SING));//sign
return token;
}
/**
* 验证token 合法性
*
*/
public static DecodedJWT verify(String token){
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
// /**
// * 获取token信息方法
// */
// public static DecodedJWT getTokenInfo(String token){
// DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
// return verify;
// }
}
/**
* JWTInterceptor是一个拦截器,用于验证请求头中的JWT令牌是否有效。
* 当有请求进入时,该拦截器会首先从请求头中获取令牌,并尝试验证其有效性。
* 如果令牌验证成功,则放行请求;否则,拦截请求并返回相应的错误信息。
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 创建一个Map对象,用于存储响应信息
Map<String, Object> map = new HashMap<>();
// 从请求头中获取令牌
String token = request.getHeader("token");
try {
JWTUtils.verify(token); // 验证令牌的有效性
return true; // 放行请求
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg", "无效签名!");
} catch (TokenExpiredException e) {
e.printStackTrace();
map.put("msg", "token过期!");
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("msg", "token算法不一致!");
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "token无效!!");
}
map.put("state", false); // 设置状态为false
// 将Map转化为JSON字符串(使用Jackson库)
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8"); // 设置响应的Content-Type
response.getWriter().println(json); // 将JSON字符串写入响应中
return false; // 不放行请求
}
}
/**
* <p>
* Mapper 接口
* </p>
*
* @author jakelihua
* @since 2023-08-14
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("select * from user where name = #{name} and password = #{password}")
User login(@Param("name") String name, @Param("password") String password);
}
/**
* <p>
* 服务类
* </p>
*
* @author jakelihua
* @since 2023-08-14
*/
public interface IUserService extends IService<User> {
User login(User user);//登录接口
}
/**
* <p>
* 服务实现类
* </p>
*
* @author jakelihua
* @since 2023-08-14
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public User login(User user) {
User userDB = userMapper.login(user.getName(), user.getPassword());
System.out.println(userDB);
if (userDB != null){
return userDB;
}
throw new RuntimeException("登录失败~~");
}
}
/**
* <p>
* 前端控制器
* </p>
*
* @author jakelihua
* @since 2023-08-14
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@GetMapping("/login")
public Result<Map<String, Object>> login(User user) {
// 打印用户名和密码
log.info("用户名: [{}]", user.getName());
log.info("密码: [{}]", user.getPassword());
// 创建结果对象
Result<Map<String, Object>> result;
try {
// 调用userService的login方法进行用户认证
User userDB = userService.login(user);
// 获取用户ID和用户名,并将其放入payload
Map<String, String> payload = new HashMap<>();
payload.put("id", userDB.getId().toString());
payload.put("name", userDB.getName());
// 生成JWT的令牌
String token = JWTUtils.getToken(payload);
// 构造成功的结果对象
result = new Result<>(200, "认证成功");
result.setData(new HashMap<>());
result.getData().put("token", token); // 响应token
} catch (Exception e) {
// 构造失败的结果对象
result = Result.fail(500, e.getMessage());
}
return result;
}
@PostMapping("/test")
public Result<Map<String, Object>> test(HttpServletRequest request) {
// 创建结果对象
Result<Map<String, Object>> result;
try {
Map<String, Object> map = new HashMap<>();
// 处理自己的业务逻辑
// 从请求头中获取token
String token = request.getHeader("token");
// 校验并解析token
DecodedJWT verify = JWTUtils.verify(token);
// 打印解析出的用户id和用户名
log.info("用户id: [{}]", verify.getClaim("id").asString());
log.info("用户name: [{}]", verify.getClaim("name").asString());
// 构造成功的结果对象
result = new Result<>(200, "请求成功!");
result.setData(map);
} catch (Exception e) {
// 构造失败的结果对象
result = Result.fail(500, e.getMessage());
}
return result;
}
}
# 9.编写测试接口
@PostMapping("/test/test")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
try {
JWTUtils.verify(token);
map.put("msg", "验证通过~~~");
map.put("state", true);
} catch (TokenExpiredException e) {
map.put("state", false);
map.put("msg", "Token已经过期!!!");
} catch (SignatureVerificationException e){
map.put("state", false);
map.put("msg", "签名错误!!!");
} catch (AlgorithmMismatchException e){
map.put("state", false);
map.put("msg", "加密算法不匹配!!!");
} catch (Exception e) {
e.printStackTrace();
map.put("state", false);
map.put("msg", "无效token~~");
}
return map;
}