目录
1. 概念
1. JWT 概述
JWT:
- 概念:- 通过 JSON 形式作为 Web 应用中的令牌,用于在各方之间安全地将信息作为 JSON 对象,安全地传输信息- 在数据传输过程中可以对数据进行加密,签名等处理- 开销小,可在多种域中使用
- 授权- 一旦用户登录,每个后续请求将包括 JWT,从而允许用户访问该令牌允许的路由、服务和资源
- 信息安全- 可对 JWT 进行签名(公钥/私钥),可验证请求发送者身份信息(认证)- JWT 签名使用表头和有效负债计算,可以验证内容是否被篡改
2. session认证流程
传统session认证:
- 概念:- http——一种无状态的协议,无法存储用户信息,用户每次请求都需要认证- 为了方便识别发起请求的用户信息,需要在服务器上存储用户信息,保存为 session,下次请求就可以识别发起请求的用户
- 演示:
@RestControllerpublicclassController{@RequestMapping("test")publicObjecttest(String name,HttpServletRequest req){ req.getSession().setAttribute("name", name);return req.getSession().getAttribute("name");}}
- 第一次请求会返回一个 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 字符串- payload:有效负载- 包含 声明:有关实体(如用户)和其他数据的声明- 在有效负载中,不要放用户的敏感信息(如:用户密码等){"name":"zhangsan""admin":true}
- 然后,使用 Base64 编码 此 JSON 对象 → payload 字符串- signature:签名- 加密:Base64编码后的 header + payload + 盐(签名),然后使用 header 中 指定的签名算法(SHA256)进行签名- 验签:根据请求中的 JWT 进行一次签名加密生成一个 signature1,然后去和请求中的 JWT 的 signature 进行比对
2. 使用JWT
依赖:
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.19.3</version></dependency>
1. 获取令牌
测试类:
public class JwtCreate {
@Testpublic 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. 验证令牌
测试方法:
@Testpublicvoidverify(){// 创建验证对象JWTVerifier verifier = JWT.require(Algorithm.HMAC256("chen1020")).build();// 验证tokenDecodedJWT 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=falseusername: root
password: admin
# 应用服务 WEB 访问端口server:port:8088
表:
实体类:
@DatapublicclassUser{privateInteger id;privateString name;privateCharacter status;privateString password;}
mapper.xml:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.chenjy.jwt_demo.mapper.UserMapper"></mapper>
mapper接口
@MapperpublicinterfaceUserMapperextendsBaseMapper<User>{}
service:
publicinterfaceUserServiceextendsIService<User>{Userlogin(User user);}
@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{@ResourceprivateUserMapper userMapper;@OverridepublicUserlogin(User user){QueryWrapper<User> wrapper =newQueryWrapper<>();
wrapper.eq("name", user.getName());User userMsg = userMapper.selectOne(wrapper);if(userMsg ==null)thrownewRuntimeException("用户不存在, 登陆失败");if(!userMsg.getPassword().equals(user.getPassword()))thrownewRuntimeException("密码错误, 登陆失败");return userMsg;}}
controller:
@RestController@Slf4jpublicclassController{@ResourceprivateUserService userService;@RequestMapping("/login")publicMap<String,Object>login(User user){Map<String,Object> map =newHashMap<>();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@Slf4jpublicclassController{@ResourceprivateUserService userService;@RequestMapping("/login")publicMap<String,Object>login(User user){Map<String,Object> map =newHashMap<>();try{User userMsg = userService.login(user);// payloadMap<String,String> payload =newHashMap<>();
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")publicMap<String,Object>test(String token){Map<String,Object> map =newHashMap<>();
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. 优化代码
拦截器:
publicclassJWTInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{// 获取请求头数据String token = request.getHeader("token");Map<String,Object> map =newHashMap<>();// 验证令牌try{JWTUtils.verify(token);returntrue;// 验证通过}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 =newObjectMapper().writeValueAsString(map);
response.setContentType("application/json;character=UTF-8");
response.getWriter().println(json);returnfalse;}}
注册拦截器:
@ConfigurationpublicclassInterceptorConfimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){
registry
.addInterceptor(newJWTInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");}}
test接口:
@RequestMapping("/test")publicMap<String,Object>test(String token){Map<String,Object> map =newHashMap<>();
map.put("state",true);
map.put("msg","请求成功");return map;}
测试:
注意: 即使重启项目,用之前的 token 依然可以请求成功。
版权归原作者 364.99° 所有, 如有侵权,请联系我们删除。