JSON Web Token
创始人
2024-02-27 08:56:30

目录

  • 1. 概念
    • 1. JWT 概述
    • 2. session认证流程
    • 2. JWT认证流程
  • 2. 使用JWT
    • 1. 获取令牌
    • 2. 验证令牌
    • 3. 封装工具类
  • 3. Springboot整合JWT
    • 1. 项目搭建
    • 2. 使用JWT
    • 3. 优化代码

1. 概念


1. JWT 概述

JWT:

  • 概念:
    • 通过 JSON 形式作为 Web 应用中的令牌,用于在各方之间安全地将信息作为 JSON 对象,安全地传输信息
    • 在数据传输过程中可以对数据进行加密,签名等处理
    • 开销小,可在多种域中使用
  • 授权
    • 一旦用户登录,每个后续请求将包括 JWT,从而允许用户访问该令牌允许的路由、服务和资源
  • 信息安全
    • 可对 JWT 进行签名(公钥/私钥),可验证请求发送者身份信息(认证)
    • JWT 签名使用表头和有效负债计算,可以验证内容是否被篡改

2. session认证流程

传统session认证:

  • 概念:

    • http——一种无状态的协议,无法存储用户信息,用户每次请求都需要认证
    • 为了方便识别发起请求的用户信息,需要在服务器上存储用户信息,保存为 cookie,下次请求就可以识别发起请求的用户
  • 演示:

    @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 被截获,用户容易受到跨站请求伪造的攻击

2. JWT认证流程

JWT认证:

  • 认证流程:

    • 前端通过 web 表单将用户名、密码发送到后端接口
    • 后端验证用户信息成功后,将用户信息作为 JWT Payload(负载),将其与头部分别进行 Base64 编码拼接后签名(JWT(Token) —— 形同 111.zzz.xxx 的字符串
    • 后端将 JWT 字符串作为登陆成功的返回结果返回给前端,前端将返回的结果存在 localStoragesessionStorage ,退出登录时,前端删除保存的 JWT
    • 前端每次请求时将 JWT 放入 http headerauthorization 位(授权位,解决 XSSXSRF 问题)
    • 后端检查请求是否存在 JWT,如果存在就验证其有效性(是否正确,是否过期,接收方是否是自己…)
    • 验证通过,就能调用后端的接口,执行业务
  • 优势:

    • 简洁:数据量小,可通过 urlpost 参数 或者 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,然后去和请求中的 JWTsignature 进行比对

2. 使用JWT

依赖:

        com.auth0java-jwt3.19.3

1. 获取令牌

测试类:

public class JwtCreate {@Testpublic void create() {Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE, 3); // 3天Map 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")); // signatureSystem.out.println(token);}
}

输出:

注意:

  • .withHeader(header) 一般不写,直接用默认配置
  • chen1020 是加密所用的盐值,自定义

2. 验证令牌

测试方法:

    @Testpublic void verify() {// 创建验证对象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 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. 项目搭建

依赖:

        com.auth0java-jwt3.19.3com.baomidoumybatis-plus-boot-starter3.4.1org.projectlomboklombokcom.alibabadruid-spring-boot-starter1.2.11mysqlmysql-connector-java

配置文件:

# 应用名称
spring:application:name: jwt_demodatasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/spbt?serverTimezone=GMT&characterEncoding=utf-8&useSSL=falseusername: rootpassword: admin# 应用服务 WEB 访问端口
server:port: 8088

表:

实体类:

@Data
public class User {private Integer id;private String name;private Character status;private String password;
}

mapper.xml:




mapper接口

@Mapper
public interface UserMapper extends BaseMapper {}

service:

public interface UserService extends IService {User login(User user);
}
@Service
public class UserServiceImpl extends ServiceImplimplements UserService{@Resourceprivate UserMapper userMapper;@Overridepublic User login(User user) {QueryWrapper 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 {@Resourceprivate UserService userService;@RequestMapping("/login")public Map login(User user) {Map 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 {@Resourceprivate UserService userService;@RequestMapping("/login")public Map login(User user) {Map map = new HashMap<>();try {User userMsg = userService.login(user);// payloadMap 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 test(String token) {Map 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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取请求头数据String token = request.getHeader("token");Map 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 {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JWTInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");}
}

test接口:

    @RequestMapping("/test")public Map test(String token) {Map map = new HashMap<>();map.put("state", true);map.put("msg", "请求成功");return map;}

测试:

注意: 即使重启项目,用之前的 token 依然可以请求成功。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...