热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

来来来~码一万字,带你读懂JWT

1.为什么要用JWT?认证在谈起JWT之前,我们先了解一下什么是认证。在登录淘宝、微博等软件或者网站之前,我们需要通过填写账号和密码来校验身份。认证是用来验证用户身份合法性的一种方

1. 为什么要用 JWT ? 认证

在谈起 JWT 之前,我们先了解一下什么是认证

在登录淘宝、微博等软件或者网站之前,我们需要通过填写账号和密码来校验身份。认证是用来验证用户身份合法性的一种方式。

那我们登录成功之后,网站如何记录我们的身份信息呢?

前面我们在学习 servlet 的时候,知道了传统的系统主要是通过 session 来存储用户的信息。session 将用户的信息存储在服务端。

但是随着用户数量的增多,服务端就需要存一堆用户的认证信息,这种方式会不断增加服务端的压力。

如果是分布式系统,用 session 存储用户信息就太拘束了。因为分布式系统一般都会做负载均衡,如果这次认证成功了,那么意味着下次请求必须仍要访问这台服务器才能认证成功。

如果是前后端分离的系统就更难受了,因为前端代码和后端代码放在不同的服务器上,除了会增加服务器的压力,还会产生跨域等一系列问题,有点得不偿失。

那有没有一种工具能帮我们解决这些认证问题?

  1. 服务端不需要存储用户的认证信息
  2. 避免跨域
  3. 保证数据的安全性

JWT闪亮登场。

2.什么是 JWT

JWT 简称: JSON Web Token,又叫做 web 应用中的令牌。它可以帮助我们完成用户的认证、存储信息、加密数据等功能。

那什么是令牌呢?令牌就相当于古代的虎符。

古代将军要想调兵遣将,必须手持虎符。

而用户要想访问系统中的某些页面,在发起的请求中必须携带使用 JWT 生成的令牌。令牌校验通过了,方可访问系统。这里的令牌简称为 token

3. JWT 的结构

注:这里所说的 JWT 的结构,指的是用 JWT 生成令牌的结构,也就是 token 的结构。

令牌的结构组成:

  • 标头(Header)
  • 载荷(Payload)
  • 签名(Signature)

令牌最终的样子是由这三部分组成的字符串:

Header.Payload.Signature

例如:

hjYGH1dajUU.dajhjksfiu2h27jjghg2.kjbhjkf982bhh2lk2 3.1 标头

标头是使用 Base64 编码将令牌类型签名算法经过加工后生成的一段字符串

标头包含两部分:

  • 令牌的类型:JWT(一般是默认的)
  • 签名算法:例如 SHA256、HMAC等

{ "alg": "HS256", "typ": "JWT" } 3.2 载荷

载荷主要存储一些自定义信息。它也是使用 Base64 编码加工后生成的一段字符串。
 

3.3 签名

签名是通过一个秘钥和标头中提供的算法再将标头和载荷进行加工后生成的一段字符串。例如:

4. JWT 的认证流程

  1. 用户点击登录,后台接收用户请求并根据账号和密码从数据库查询用户信息。用户若存在,则使用 JWT 生成 token 并返回给前台。用户若不存在,则返回错误信息。
  2. 前端在请求其他资源时将 token 放到请求头中。
  3. 后台从请求头中获取 token 信息,如果 token 校验失败,则返回错误信息。如果校验成功,就将业务数据返回给前端。
5. JWT 的使用

1.引入依赖

com.auth0 java-jwt 3.10.3

2.生成token

public static void main(String[] args) { Date date = new Date(System.currentTimeMillis() +1000); Algorithm algorithm = Algorithm.HMAC256("!Secret"); String token = JWT.create() .withClaim("name", "张三") .withExpiresAt(date) // 设置过期时间 .sign(algorithm); // 设置签名算法 System.out.println(token); }

生成结果:

"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. eyJuYW1lIjoi5byg5LiJIiwiZXhwIjoxNjUwNzE1OTg3fQ. -hrxN6RwXCPnI-pmQaIsetx-8iN98XwZczmTFBoV1FI"

  1. 校验 token

校验 token,其实就是比较 token 是否真确,如果不正确程序就会报错。

String token = ""; JWT.require(Algorithm.HMAC256("!Secret")).build().verify(token);

  1. 获取 token 中的载荷信息

String token=" "; DecodedJWT jwt = JWT.decode(token); String name = jwt.getClaim("name").asString();

  1. 判断 token 是否过期

public boolean isExpire(String token) { DecodedJWT jwt = JWT.decode(token); // 如果token的过期时间小于当前时间,则表示已过期,为true return jwt.getExpiresAt().getTime() 6. JWT 工具类

因为 JWT 的作用主要是生成 token、校验 token、获取token中存储的自定义信息,所以我们一般会把 JWT 封装成一个工具类。

public class JwtUtil { // 秘钥 private static final String SECRET = "SECRET_PRIVATE!"; private static final long TIME_UNIT = 1000; // 生成包含用户id的token public static String createJwtToken(String userId, long expireTime) { Date date = new Date(System.currentTimeMillis() + expireTime * TIME_UNIT); Algorithm algorithm = Algorithm.HMAC256(SECRET); return JWT.create() .withClaim("userId", userId) .withExpiresAt(date) // 设置过期时间 .sign(algorithm); // 设置签名算法 } // 生成包含自定义信息的token public static String createJwtToken(Map map, long expireTime) { JWTCreator.Builder builder = JWT.create(); if (MapUtil.isNotEmpty(map)) { map.forEach((k, v) -> { builder.withClaim(k, v); }); } Date date = new Date(System.currentTimeMillis() + expireTime * TIME_UNIT); Algorithm algorithm = Algorithm.HMAC256(SECRET); return builder .withExpiresAt(date) // 设置过期时间 .sign(algorithm); // 设置签名算法 } // 校验token,其实就是比较token public static DecodedJWT verifyToken(String token) { // 如果校验失败,程序会抛出异常 return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token); } // 从token中获取用户id public static String getUserId(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("userId").asString(); } catch (JWTDecodeException e) { return null; } } // 从token中获取定义的荷载信息 public static String getTokenClaim(String token, String key) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim(key).asString(); } catch (JWTDecodeException e) { return null; } } // 判断 token 是否过期 public static boolean isExpire(String token) { DecodedJWT jwt = JWT.decode(token); // 如果token的过期时间小于当前时间,则表示已过期,为true return jwt.getExpiresAt().getTime() 7. JWT 案例

这里我们通过一个 springboot 项目来感受一下 JWT 的使用过程。

  • 开发工具:IDEA
  • 技术栈:SpringBoot、MyBatisPlus、JWT
  • 数据库:Mysql
7.1 用户登录 7.1.1 创建 SpringBoot 项目

7.1.2 引入依赖

org.springframework.boot spring-boot-starter-web mysql mysql-connector-java runtime org.projectlombok lombok true com.baomidou mybatis-plus-boot-starter 3.4.0 com.auth0 java-jwt 3.10.3 cn.hutool hutool-all 5.5.7 org.springframework.boot spring-boot-starter-test test 7.1.3 application.yml

server: port: 8082 servlet: context-path: /jwt-demo spring: # 数据源 datasource: url: jdbc:mysql://localhost:3306/jwt_demo?allowPublicKeyRetrieval=true&useSSL=false username: root password: 12345678 driver-class-name: com.mysql.cj.jdbc.Driver # MybatisPlus mybatis-plus: global-config: db-config: field-strategy: IGNORED column-underline: true logic-delete-field: isDeleted # 全局逻辑删除的实体字段名 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) db-type: mysql id-type: assign_id mapper-locations: classpath*:/mapper/**Mapper.xml type-aliases-package: com.zhifou.entity configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志 7.1.4 创建用户表

CREATE TABLE `t_user` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名', `password` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; 7.1.5 用户实体类

@Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) @TableName("t_user") public class User implements Serializable { private static final long serialVersiOnUID= 1L; /** * id */ @TableId(value = "id", type = IdType.ASSIGN_ID) private Long id; /** * 姓名 */ private String username; /** * 密码 */ private String password; } 7.1.6 Service 和实现类

UserService

public interface UserService extends IService { /** * 登录 * @param user * @return */ User login(User user); }

@Service public class UserServiceImpl extends ServiceImpl implements UserService { @Override public User login(User user) { User userOne= this.getOne(new QueryWrapper().eq("username", user.getUsername()) .eq("password", user.getPassword())); return null == userOne ? null : userOne; } } 7.1.7 Dao 和 mapper.xml

UserMapper

public interface UserMapper extends BaseMapper { }

UserMapper.xml

id, username, password, sex, age 7.1.8 登录接口

@PostMapping("/login") public Map login(@RequestBody User user) { Map data = new HashMap<>(); User userOne= userService.login(user); if (null != userOne) { data.put("code", 200); data.put("msg", "登陆成功"); data.put("token", JwtUtil.createJwtToken(userOne.getId().toString(), 24 * 10)); } else { data.put("code", 400); data.put("msg", "账号或者密码错误"); } return data; } 7.1.9 测试

登录成功

登录失败

7.2 登录成功访问其他资源

用户登录成功后,我们把 token 返回给了前端。用户再次访问该网站的其他资源,我们怎么判断当前的用户和上次登录成功后的用户是同一个用户呢?

只需要两步:

  • 前端:请求头中携带 token
  • 后端:配置拦截器,校验 token
7.2.1 创建拦截器

MyInterceptor

/** * @Desc: * @Author: 知否技术 * @date: 下午7:11 2022/4/24 */ public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); Map result = new HashMap<>(); try { // 校验token,校验失败会抛出异常 JwtUtil.verifyToken(token); return true; } catch (TokenExpiredException e) { e.printStackTrace(); result.put("code", "500"); result.put("msg", "token已过期"); } catch (Exception e) { e.printStackTrace(); result.put("code", "500"); result.put("msg", "token无效"); } response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(JSONUtil.parse(result)); return false; } } 7.2.2 拦截器配置类

/** * @Desc: * @Author: 知否技术 * @date: 下午7:18 2022/4/24 */ @Component public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()) // 拦截所有请求 .addPathPatterns("/**") // 排除路径,比如用户登录、退出等 .excludePathPatterns("/user/login"); } } 7.2.3 测试

token 校验失败

token 校验成功

7.2.4 完整代码

链接: https://pan.baidu.com/s/1sVRXHgePD6H_a7RPqsGJCw 提取码: wvr7

推荐阅读
  • 本文介绍了NetCore WebAPI开发的探索过程,包括新建项目、运行接口获取数据、跨平台部署等。同时还提供了客户端访问代码示例,包括Post函数、服务器post地址、api参数等。详细讲解了部署模式选择、框架依赖和独立部署的区别,以及在Windows和Linux平台上的部署方法。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 31.项目部署
    目录1一些概念1.1项目部署1.2WSGI1.3uWSGI1.4Nginx2安装环境与迁移项目2.1项目内容2.2项目配置2.2.1DEBUG2.2.2STAT ... [详细]
  • svnWebUI:一款现代化的svn服务端管理软件
    svnWebUI是一款图形化管理服务端Subversion的配置工具,适用于非程序员使用。它解决了svn用户和权限配置繁琐且不便的问题,提供了现代化的web界面,让svn服务端管理变得轻松。演示地址:http://svn.nginxwebui.cn:6060。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • phpcomposer 那个中文镜像是不是凉了 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • uniapp开发H5解决跨域问题的两种代理方法
    本文介绍了uniapp开发H5解决跨域问题的两种代理方法,分别是在manifest.json文件和vue.config.js文件中设置代理。通过设置代理根域名和配置路径别名,可以实现H5页面的跨域访问。同时还介绍了如何开启内网穿透,让外网的人可以访问到本地调试的H5页面。 ... [详细]
  • 本文介绍了如何使用jQuery和AJAX来实现动态更新两个div的方法。通过调用PHP文件并返回JSON字符串,可以将不同的文本分别插入到两个div中,从而实现页面的动态更新。 ... [详细]
  • Gitlab接入公司内部单点登录的安装和配置教程
    本文介绍了如何将公司内部的Gitlab系统接入单点登录服务,并提供了安装和配置的详细教程。通过使用oauth2协议,将原有的各子系统的独立登录统一迁移至单点登录。文章包括Gitlab的安装环境、版本号、编辑配置文件的步骤,并解决了在迁移过程中可能遇到的问题。 ... [详细]
  • 微信官方授权及获取OpenId的方法,服务器通过SpringBoot实现
    主要步骤:前端获取到code(wx.login),传入服务器服务器通过参数AppID和AppSecret访问官方接口,获取到OpenId ... [详细]
author-avatar
fffas2010_734_196
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有