什么是JWT
-
起源:
了解一门技术,应当从为什么产生开始了解比较好,JWT主要用于用户登录鉴权。解决分布式、单点登录,用户认证问题。
认证技术还有:session认证,token认证… -
描述:
1、客户端:用户使用账号、密码发送登录请求,
服务端:查询数据库验证当前用户是否存在,存在,生成token返回给客户端
2、客户端:将返回的token存储到本地
3、客户端:请求一个查询功能(在request里携带存储的token字符串),
服务端:解析request中携带的token是否正确,正确,返回查询数据,错误,返回错误信息,
客户端:正确,则展示数据,错误,跳转登录页面 -
简单来说就是一种认证机制,让后台知道该请求是来自于授信客户端。如图:
JWT数据结构
jwt生成的字符串格式
xxx.yyy.zzz
Header.PayLoad.Signature
//xxx对应:jwt头(Header)
//yyy对应:有效负荷(PayLoad)
//zzz对应:签名(Signature)
-
Header
对应xxx,它是一个描述JWT原生的JSON对象,如下{ "alg": "HS256", "typ": "JWT" }
alg表示签名使用的算法,默认为HMAC SHA256(HS256),type为令牌类型,JWT统一写为JWT。
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。 -
PayLoad
对应yyy,也是一个json对象,除了自定义需要传递的数据外,还有七个默认的字段。
分别是,iss:发行人、exp:到期时间、sub:主题、aud:用户、nbf:在此之前不可用、iat:发布时间、jti:JWT ID用于标识该JWT。{ // 默认字段 "sub": "主题", // 自定义字段 "user": "xiao.hei", "id": "1" }
需要注意的是,默认情况下JWT是未加密的,任何人都可以解读其内容,因此如果一些敏感信息不要存放在此,以防信息泄露。
JSON对象也使用Base64 URL算法转换为字符串保存。
-
Signature
对应zzz,对应签名,生成方式,首先在代码里自定义一个密匙,该密匙仅存在于服务器中,保证不让用户知道(密匙泄露,会造成破解登录问题),然后使用Header指定的算法对Header和Payload进行计算,然后算出一个签名哈希,也就是Signature验证token操作:例用JWT前两端,用一套哈希算法和同一个密匙计算一个签名值,然后把计算的签名和收到的JWT第三段比较,相同则认证通过。
JWT优点
- json格式的通用性,使其可以跨语言
- 可以在payload存储一些非敏感信息
- 便于传输,jwt结构简单,字节占用小
- 不需要在服务端保存会话信息,易于应用扩展。
JWT缺点
- 安全性无法保证,所以jwt的PayLoad不能存储敏感信息,因为PayLoad并没有加密,只是用Base64编码而已。
- 无法中途放弃,因为一旦签发了一个jwt,那么在到期前始终有效,如果用户信息更新了,只能等旧的jwt到期后重新签发一个。
- 续期问题,当签发一个jwt后,客户一直在操作页面,按理jwt应一致有效,但当jwt有效期过了,用户需要重新登录。
JWT项目中实操(单服务中)ps:及拦截器实现拦截
- 导入maven: 使用springboot+mybatis-plus+mysql来实操
-
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <!-- <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> --> <!-- jwt依赖 --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
3. 操作相关:
yml配置,修改数据库ip及库
server:
port: 80
spring:
datasource:
username: root
password: p@ssW0rd
url: jdbc:mysql://lcoalhost:3306/heiaxin?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true
driver-class-name: com.mysql.cj.jdbc.Driver
启动类
@SpringBootApplication
@MapperScan("com.heiaxin.jwtproject.mapper") // 配置自己的mapper扫描路径
public class JwtprojectApplication {
public static void main(String[] args) {
SpringApplication.run(JwtprojectApplication.class, args);
}
}
JWT工具类
package com.heiaxin.jwtproject.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.heiaxin.jwtproject.entity.User;
import java.util.Calendar;
/**
* json web token工具类
*/
public class JwtUtils {
// 密匙
private static final String SIGN = "hfahdfHLFHASKLFH4636990";
/**
* 根据用户对象生产token
*/
public static String getToken(User user) {
// 设置过期时间
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, 7);
// 创建jwt builder
JWTCreator.Builder builder = JWT.create();
// 设置token,第二部分存储的值
builder.withClaim("username", user.getUName());
builder.withClaim("id", user.getId());
// 设置过期时间
String token = builder.withExpiresAt(cal.getTime()).sign(Algorithm.HMAC256(SIGN));
return token;
}
/**
* 验证token合法性,及获取token中保存的用户基本信息
*/
public static DecodedJWT verify (String token) {
return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
}
控制器
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/findAll")
public Map<String, Object> findALl() {
Map<String, Object> result = new HashMap<>();
User user = userService.getById(1);
String token = JwtUtils.getToken(user);
System.out.println("当前token值====>" + token);
DecodedJWT verify = JwtUtils.verify(token);
System.out.println(verify.getClaim("id"));
System.out.println("token转译===> " + verify.toString());
result.put("token", token);
result.put("state", true);
result.put("msg", "登录成功");
return result;
}
@PostMapping("/test")
public Map<String, Object> test(String token) {
Map<String, Object> result = new HashMap<>();
result.put("msg", "认证成功!");
result.put("state", true);
return result;
}
}
自定义拦截器
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String, Object> result = new HashMap<>();
String token = request.getHeader("token");
try {
DecodedJWT verify = JwtUtils.verify(token);
System.out.println("获取token,payLoad中的用户id====》" + id);
return true;
} catch (SignatureVerificationException e) {
result.put("msg", "无效签名!");
e.printStackTrace();
} catch (TokenExpiredException e) {
result.put("msg", "token过期!");
e.printStackTrace();
} catch (AlgorithmMismatchException e) {
result.put("msg", "token算法不一致!");
e.printStackTrace();
} catch (Exception e) {
result.put("msg", "token无效");
e.printStackTrace();
}
// 将有效的token写回response
// 将map 转为json jackson
String json = new ObjectMapper().writeValueAsString(result);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
使自定义拦截器生效
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor()) // 添加自定义拦截器
.addPathPatterns("/user/test") // 需要走拦截器的接口
.excludePathPatterns("/user/login"); // 不需要走拦截器的接口
}
}
测试
可以正常get到token
测试test
将get到的token,带入到post请求的header中