目录
3.3、创建一个JwtUtil类在类里创建生成jwt和解析jwt的方法,方便后来直接调用方法
3.4、在登录接口调用createJWT方法来生成JWT,将jwt返回给前端,后续的每次请求都会携带JWT
3.5、定义JwtTokenAdminInterceptor拦截器,对登录接口不拦截,其他接口要进行JWT校验,校验通过即可访问
一、什么是JWT
JSON Web Tokens(JWT)是一种用于在网络应用环境间传递声明的一种紧凑的、URL安全的方式。JWT可以被用来在身份验证和信息交换中安全地在服务之间传递信息
二、JWT的结构
JWT由三部分组成,分别是Header(头部)、Payload(负载)和Signature(签名):
- Header(头部):通常包含两部分:token的类型(这里是JWT)和所使用的签名算法(如HS256或RS256)。
- Payload(负载):包含所谓的Claims(声明),它们是关于实体(通常是用户)和其他数据的声明。例如,可以包含用户的ID、用户名、角色等。
- Signature(签名):用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的token,还可以验证发送者的身份。
三、JWT在Java中的实现
3.1、添加依赖
首先,需要在项目的pom.xml
文件中添加JWT库的依赖。常用的库有jjwt
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
3.2、创建一个JwtProperties类
用来映射yml文件关于jwt的参数,进行全局配置
@Compoent注解,是把该类交给ioc容器管理
@ConfigurationProperties是完成yml文件的映射,prefix="sky.jwt"是标明参数前缀、
@Data是lombok为该类生成get和set方法用来获取参数的值
@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {
/**
* 管理端员工生成jwt令牌相关配置
*/
private String adminSecretKey;
private long adminTtl;
private String adminTokenName;
在yml文件对参数进行赋值
#自定义的,于java类相绑定的
sky:
jwt:
# 设置jwt签名加密时使用的秘钥
admin-secret-key: itcast
# 设置jwt过期时间,二十个小时
admin-ttl: 72000000
# 设置前端传递过来的令牌名称
admin-token-name: token
3.3、创建一个JwtUtil类在类里创建生成jwt和解析jwt的方法,方便后来直接调用方法
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
*
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
3.4、在登录接口调用createJWT方法来生成JWT,将jwt返回给前端,后续的每次请求都会携带JWT
/**
* 登录
*
* @param employeeLoginDTO
* @return
*/
@ApiOperation(value = "员工登录")
@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);
Employee employee = employeeService.login(employeeLoginDTO);
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
//token就是由createJWT方法生产的jwt令牌w
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)//将jwt封装到VO返回给前端
.build();
//进行返回
return Result.success(employeeLoginVO);
}
3.5、定义JwtTokenAdminInterceptor拦截器,对登录接口不拦截,其他接口要进行JWT校验,校验通过即可访问
//注入JwtProperties对象
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
//调用线程空间的方法 将解析出来jwt令牌的empID存入这个单独空间(每个用户的请求都是一个单独的线程)
BaseContext.setCurrentId(empId);
//log.info("当前线程为:"+BaseContext.getCurrentId());
log.info("当前员工id:{}", empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
3.6、将自定义的拦截器进行注册
创建一个WebMvcConfiguration实现WebMvcConfigurationSupport接口
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
/**
* 注册自定义拦截器
*
* @param registry
*/
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
}
四、JWT的优点和缺点
优点:
- 安全性高:基于Token的验证机制,可以避免敏感信息在Cookie中的存储,降低被窃取的风险
- 无状态和可扩展性:服务器不需要存储Session信息,每个请求都带有Token,减少了服务器的会话管理开销
- 性能:在网络传输过程中,性能表现更佳
缺点:
- Token续签问题:需要设计合理的过期时间和刷新机制,以避免用户频繁重新登录
- 存储安全:Token存储在客户端,存在被XSS攻击窃取的风险,建议使用HTTP Only Cookie或专门的安全存储机制
- 无法在服务端注销:一旦JWT发出,服务端无法主动使它失效,除非增加额外逻辑,如黑名单机制