1. 概念
1. JWT 概述
JWT:
-
概念:
- 通过 JSON 形式作为 Web 应用中的令牌,用于在各方之间安全地将信息作为 JSON 对象,安全地传输信息
- 在数据传输过程中可以对数据进行加密,签名等处理
- 开销小,可在多种域中使用
-
授权
- 一旦用户登录,每个后续请求将包括 JWT,从而允许用户访问该令牌允许的路由、服务和资源
-
信息安全
- 可对 JWT 进行签名(公钥/私钥),可验证请求发送者身份信息(认证)
- JWT 签名使用表头和有效负债计算,可以验证内容是否被篡改
2. session认证流程
传统session认证:
-
概念:
- http——一种无状态的协议,无法存储用户信息,用户每次请求都需要认证
- 为了方便识别发起请求的用户信息,需要在服务器上存储用户信息,保存为 session,下次请求就可以识别发起请求的用户
-
演示:
@RestController public class Controller { @RequestMapping("test") public Object test(String name, HttpServletRequest req) { req.getSession().setAttribute("name", name); return req.getSession().getAttribute("name"); } }
- 第一次请求会返回一个 cookie
后续请求将不再返回cookie
- 第一次请求会返回一个 cookie
-
缺陷:
- 每个用户请求之后,都需要在服务端做一次记录(保存在内存中),开销较大
- 认证的记录保存在上次访问的服务器的内存中,扩展能力弱
-
cookie 被截获,用户容易受到跨站请求伪造的攻击
…
2. JWT认证流程
JWT认证:
-
认证流程:
- 前端通过 web 表单将用户名、密码发送到后端接口
- 后端验证用户信息成功后,将用户信息作为 JWT Payload(负载),将其与头部分别进行 Base64 编码拼接后签名(JWT(Token) —— 形同 111.zzz.xxx 的字符串)
- 后端将 JWT 字符串作为登陆成功的返回结果返回给前端,前端将返回的结果存在 localStorage 和 sessionStorage ,退出登录时,前端删除保存的 JWT
- 前端每次请求时将 JWT 放入 http header 的 authorization 位(授权位,解决 XSS 和 XSRF 问题)
- 后端检查请求是否存在 JWT,如果存在就验证其有效性(是否正确,是否过期,接收方是否是自己…)
- 验证通过,就能调用后端的接口,执行业务
-
优势:
- 简洁:数据量小,可通过 url、post 参数 或者 http header 发送
- 自包含:负载中包含了用户所需要的信息,不需要多次查询数据库
- 采用 JSON 加密的形式保存在客户端,跨语言
- 不需要在服务器保存会话信息(避免内存占用),适用于微服务
-
结构:
- 3段式字符串:header.payload.signature
-
header:标头
- 通常包含两部分:签名使用的算法(HMAC、SHA256(默认)、RSA) + 令牌类型
{ "alg": "SHA256", "type": "JWT" }
- 然后,使用 Base64 编码 此 JSON 对象 → header 字符串
- 通常包含两部分:签名使用的算法(HMAC、SHA256(默认)、RSA) + 令牌类型
-
payload:有效负载
- 包含 声明:有关实体(如用户)和其他数据的声明
- 在有效负载中,不要放用户的敏感信息(如:用户密码等)
{ "name": "zhangsan" "admin": true }
- 然后,使用 Base64 编码 此 JSON 对象 → payload 字符串
-
signature:签名
- 加密:Base64编码后的 header + payload + 盐(签名),然后使用 header 中 指定的签名算法(SHA256)进行签名
- 验签:根据请求中的 JWT 进行一次签名加密生成一个 signature1,然后去和请求中的 JWT 的 signature 进行比对
-
header:标头
- 3段式字符串:header.payload.signature
2. 使用JWT
依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.3</version>
</dependency>
1. 获取令牌
测试类:
public class JwtCreate {
@Test
public void create() {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 3); // 3天
Map<String, Object> header = new HashMap<>();
header.put("alg", "SHA256");
header.put("type", "JWT");
String token = JWT.create()
.withHeader(header) // header
.withClaim("uid", "001") // payload
.withClaim("name", "zhangsan") // payload
.withClaim("admin", true) // payload
.withExpiresAt(instance.getTime()) // 指定令牌过期时间(3天后过期)
.sign(Algorithm.HMAC256("chen1020")); // signature
System.out.println(token);
}
}
输出:
注意:
-
.withHeader(header)
一般不写,直接用默认配置 - chen1020 是加密所用的盐值,自定义
2. 验证令牌
测试方法:
@Test
public void verify() {
// 创建验证对象
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("chen1020")).build();
// 验证token
DecodedJWT verify = verifier.verify("eyJ0eXAiOiJKV1QiLCJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJ1aWQiOiIwMDEiLCJuYW1lIjoiemhhbmdzYW4iLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjY5ODc1MTI3fQ.-eewHSjiqXQggakhJayx2mOfqmRS8iz4Ockb_BKg_0o");
// 获取用户信息(验证通过后才能获取)
System.out.println(verify.getClaims());
System.out.println("-------------------------------------");
System.out.println("uid: " + verify.getClaims().get("uid") + " name: " + verify.getClaims().get("name"));
System.out.println("-------------------------------------");
System.out.println("uid: " + verify.getClaim("uid") + " name: " + verify.getClaim("name"));
}
输出:
注意:
- 一个
withClaim
中,一个 key 只能对应一个 value,后面的 value 会覆盖前面的 value - 可以使用
withArrayClaim(String name, xxx[] xxx)
,放多个 value
验证令牌的过程: 验证签名(SignatureVerificationException
) → token是否过期(TokenExpiredException
) → 签名算法(AlgorithmMismatchException
)
3. 封装工具类
public class JWTUtils {
// 盐值
private static final String SALT = "LyeXro0VaE^!p";
/**
* @Description 创建token
* @param map 负载map
* @return String
*/
public static String getToken(Map<String, String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 5);
JWTCreator.Builder builder = JWT.create();
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
String token = builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SALT));
return token;
}
/**
* @Description 验证token合法性,不合法就会抛出异常
* @param token
*/
public static DecodedJWT verify(String token) {
return JWT.require(Algorithm.HMAC256(SALT)).build().verify(token);
}
}
3. Springboot整合JWT
1. 项目搭建
依赖:
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.3</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.11</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置文件:
# 应用名称
spring:
application:
name: jwt_demo
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spbt?serverTimezone=GMT&characterEncoding=utf-8&useSSL=false
username: root
password: admin
# 应用服务 WEB 访问端口
server:
port: 8088
表:
实体类:
@Data
public class User {
private Integer id;
private String name;
private Character status;
private String password;
}
mapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chenjy.jwt_demo.mapper.UserMapper">
</mapper>
mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
service:
public interface UserService extends IService<User> {
User login(User user);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService{
@Resource
private UserMapper userMapper;
@Override
public User login(User user) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", user.getName());
User userMsg = userMapper.selectOne(wrapper);
if (userMsg == null) throw new RuntimeException("用户不存在, 登陆失败");
if (!userMsg.getPassword().equals(user.getPassword())) throw new RuntimeException("密码错误, 登陆失败");
return userMsg;
}
}
controller:
@RestController
@Slf4j
public class Controller {
@Resource
private UserService userService;
@RequestMapping("/login")
public Map<String, Object> login(User user) {
Map<String, Object> map = new HashMap<>();
try {
User userMsg = userService.login(user);
map.put("state", true);
map.put("smg", "登陆成功");
} catch (RuntimeException e) {
map.put("state", false);
map.put("smg", "登陆失败");
e.printStackTrace();
}
return map;
}
}
测试:
2. 使用JWT
@RestController
@Slf4j
public class Controller {
@Resource
private UserService userService;
@RequestMapping("/login")
public Map<String, Object> login(User user) {
Map<String, Object> map = new HashMap<>();
try {
User userMsg = userService.login(user);
// payload
Map<String, String> payload = new HashMap<>();
payload.put("id", userMsg.getId() + "");
payload.put("name", userMsg.getName());
payload.put("status", userMsg.getStatus() + "");
// 生成令牌
String token = JWTUtils.getToken(payload);
map.put("state", true);
map.put("smg", "登陆成功");
map.put("token", token);
} catch (RuntimeException e) {
map.put("state", false);
map.put("smg", "登陆失败");
e.printStackTrace();
}
return map;
}
@RequestMapping("/test")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
log.info("token:" + token);
try {
JWTUtils.verify(token);
map.put("state", true);
map.put("smg", "登陆成功");
} catch (SignatureVerificationException e) {
map.put("state", false);
map.put("smg", "签名错误");
} catch (TokenExpiredException e) {
map.put("state", false);
map.put("smg", "token过期");
} catch (AlgorithmMismatchException e) {
map.put("state", false);
map.put("smg", "算法不一致");
} catch (Exception e) {
map.put("state", false);
map.put("smg", "无效签名");
}
return map;
}
}
3. 优化代码
拦截器:
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求头数据
String token = request.getHeader("token");
Map<String, Object> map = new HashMap<>();
// 验证令牌
try {
JWTUtils.verify(token);
return true; // 验证通过
} catch (SignatureVerificationException e) {
map.put("state", false);
map.put("smg", "签名错误");
} catch (TokenExpiredException e) {
map.put("state", false);
map.put("smg", "token过期");
} catch (AlgorithmMismatchException e) {
map.put("state", false);
map.put("smg", "算法不一致");
} catch (Exception e) {
map.put("state", false);
map.put("smg", "无效签名");
}
map.put("state", false);
// 将map转换为json
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;character=UTF-8");
response.getWriter().println(json);
return false;
}
}
注册拦截器:
@Configuration
public class InterceptorConf implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
test接口:
@RequestMapping("/test")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
map.put("state", true);
map.put("msg", "请求成功");
return map;
}
测试:
文章来源:https://www.toymoban.com/news/detail-615605.html
注意: 即使重启项目,用之前的 token 依然可以请求成功。文章来源地址https://www.toymoban.com/news/detail-615605.html
到了这里,关于JSON Web Token的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!