JWT结构详解与JWT设置

news2024/11/16 11:38:29

JWT结构详解与JWT设置

  • 1. 什么是token
  • 2. 为什么要使用token
  • 3. 什么是JWT
  • 4. JWT的格式
    • 4.1 header
    • 4.2 payload
    • 4.3 signature
  • 5. JWT校验流程
  • 6. JWT使用案例
    • 6.1 token的创建
    • 6.2 判断token是否可以刷新
    • 6.3 刷新token
    • 6.4 token的校验
    • 6.5 用户验证流程

1. 什么是token

Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务端生成一个token并返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。

2. 为什么要使用token

大家可能会想到,用服务器的session_id存储到cookies中也能做到,为什么非要用token呢?
个人觉得,开发web应用的话用哪种都行。但如果是开发api接口,前后端分离,最好使用token,因为 session + cookies 是基于web的。但是针对 api接口,需要考虑到移动端,app是没有cookies和session的。

3. 什么是JWT

JWT是 JSON Web Tokens 的简称,从单词可以看出它也是一种 token,其实可以理解为一种生成token的框架或规范。

4. JWT的格式

在这里插入图片描述

4.1 header

在这里插入图片描述
非常简单,typ顾名思义就是type的意思,例如上面这里就指明是JWT的类型。alg顾名思义是algorithm的意思,指代一个加密算法,例如上面指代HS256(HMAC-SHA256),这个算法会在生成第三部分signature的时候用到。

4.2 payload

payload 用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的一组声明,这些声明被JWT标准称为 claims
payload 的一个“属性值对”其实就是一个claim(要求),每一个 claim 都代表特定的含义和作用。
根据JWT的标准,这些 claims 可以分为以下三种类型:

  • Reserved claims(保留)
    它属于JWT标准里面规定的一些claim,就像是编程语言的保留字一样。JWT标准里面定义好的claim有:

    iss(Issuser):代表这个JWT的签发主体;
    sub(Subject):代表这个JWT的主体,即它的所有人;
    aud(Audience):代表这个JWT的接收对象;
    exp(Expiration time):是一个时间戳,代表这个JWT的过期时间;
    nbf(Not Before):是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的;
    iat(Issued at):是一个时间戳,代表这个JWT的签发时间;
    jti(JWT ID):是JWT的唯一标识。

  • Public claims,略(不重要)

  • Private claims(私有)
    这个指的就是自定义的claim。这些claim跟JWT标准规定的claim区别在于:

    • JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(即JWT的每个实现库都会参照这个描述来提供JWT的验证实现);
    • 而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行;
    • 按照JWT标准的说明:保留的claims都是可选的,在生成payload不强制用上面的那些claim;

4.3 signature

在这里插入图片描述
signature顾名思义就是签名,签名一般就是用一些算法生成一个能够认证身份的字符串,具体算法就是上面表示的,也比较简单,唯一说明的一点是上面 hash 方法用到了一个 secret,这个东西需要 客户端 和 服务端 双方都知道,相当于约好了同一把验证的钥匙,最终才好做认证。

最后,每个部分都做一个 base64url encoded 的转换,然后按照 header.payload.signature 这个格式串起来就行了

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

注意:JWT不保证数据不泄露,因为JWT的设计目的就不是数据加密和保护,而是为了认证来源。

5. JWT校验流程

最后再解释一下服务端 如何认证用户发来的JWT是否合法。

首先服务端和客户端必须要有个约定,例如双方同时知道加密用的 secret(这里假设用的就是简单的对称加密算法),那么在服务端 收到这个JWT时,就可以利用 JWT 前两段数据作为输入,用同一套 hash 算法和同一个 secret 自己计算一个签名值,然后把计算出来的签名值和收到的 JWT 第三段比较,如果相同则认证通过,如果不相同,则认证不通过。

就这么简单,当然,上面是假设了这个hash算法是对称加密算法,其实如果用非对称加密算法也是可以的,比方说我就用非对称的算法,对应的密钥就是一对,而非一个,那么一对公钥+私钥可以这样分配:私钥由客户端保存,公钥由服务端保存,服务端验证的时候,用公钥解密收到的 signature,这样就得到了 header 和 payload 的拼接值,用这个拼接值跟前两段比较,相同就验证通过。

6. JWT使用案例

6.1 token的创建

   /**
     * 初始化生成token的参数
     * @param userId
     * @return String
     */
    public String generateToken(String userId) {
        Map<String, Object> claims = new HashMap<>(1);
        claims.put("sub", userId);
        return generateToken(claims);
    }
 
    /**
     * 生成token
     * @param claims
     * @return String
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(this.generateExpirationDate())
                .setIssuedAt(this.generateCurrentDate())
                .signWith(SignatureAlgorithm.HS512, this.secret)
                .compact();
    }

6.2 判断token是否可以刷新

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(this.secret)
                    .parseClaimsJws(token)
                    .getBody();
            final Date iat = claims.getIssuedAt();
            final Date exp = claims.getExpiration();
            if (iat.before(lastPasswordReset) || exp.before(generateCurrentDate())) {
                return false;
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

6.3 刷新token

public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = Jwts.parser()
                    .setSigningKey(this.secret)
                    .parseClaimsJws(token)
                    .getBody();
            refreshedToken = this.generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

6.4 token的校验

    public String verifyToken(String token) {
        String result = "";
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(this.secret)
                    .parseClaimsJws(token)
                    .getBody();
            result = TokenStatus.TOKEN_VALID;
        } catch (Exception e) {
            result = TokenStatus.TOKEN_INVALID;
        }
        return result;
    }

6.5 用户验证流程

用户登录

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public JSONResponse login(@RequestBody Map<String, String> map) {
        String loginName = map.get("loginName");
        String password = map.get("password");
        User user1 = new User();
        user1.setName(loginName);
        user1.setPassword(password);
        //身份验证是否成功
        boolean isSuccess = userService.checkUser(user1);
        if (isSuccess) {
            User user = userService.getUserByLoginName(loginName);
            if (user != null) {
                //生成token,返回给客户端
                String token = jwtUtil.generateToken(user.getId());
                if (token != null) {
                    return JSONResponse.ok(token);
                }
            }
        }
        //返回登陆失败消息
        return JSONResponse.info("登陆失败");
    }

拦截器验证

 @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String token = request.getHeader("access_token");
        //token是否存在
        if (null != token) {
            //验证token是否正确
            String result = jwtUtil.verifyToken(token);
            if(result.equals(TokenStatus.TOKEN_INVALID)){//无效
                outputStream(servletResponse,"token令牌无效...");
            }else{//有效令牌,需要重新刷新token,再将token传回客户端,客户端会拿着新的token进行访问
                String refreshedToken = jwtUtil.refreshToken(token);
                System.out.println("refreshedToken:"+refreshedToken);
                // Access-Control-Allow-Origin就是我们需要设置的域名
                // Access-Control-Allow-Headers跨域允许包含的头。
                // Access-Control-Allow-Methods是允许的请求方式
//                response.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
//                response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
                // response.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type, Accept");
                // 允许请求头Token
                // httpResponse.setHeader("Access-Control-Allow-Headers","Origin,X-Requested-With, Content-Type, Accept, Token");
                // 允许客户端,发一个新的请求头jwt
//                response.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With, Content-Type, Accept, jwt");
                // 允许客户端,处理一个新的响应头jwt
//                response.setHeader("Access-Control-Expose-Headers", "jwt");
                response.setHeader("access_token", refreshedToken);
            }
            filterChain.doFilter(request, response);
            return;
        }
        outputStream(servletResponse,"无token令牌...");
    }

    /**
     * @description: 向客户端返回响应信息(json格式)
     * @author wangdong
     * @date 2019/10/8 16:46
     */
    private void outputStream(ServletResponse servletResponse,String message){
        try{
            String string = JSON.toJSONString(JSONResponse.info(message));
            servletResponse.setContentType("application/json;charset=UTF-8");
            servletResponse.getOutputStream().write(string.getBytes("UTF-8"));
            servletResponse.getOutputStream().close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

--------over

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2085994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Prometheus+Grafana的安装和入门

概念 什么是Prometheus? Prometheus受启发于Google的Brogmon监控系统&#xff08;相似kubernetes是从Brog系统演变而来&#xff09;&#xff0c; 从 2012年开始由google工程师Soundclouds使用Go语言开发的开源监控报警系统和时序列数据库 (TSDB)。&#xff0c;并且与2015年早…

2017年系统架构师案例分析试题一

目录 案例 【题目】 【问题 1】(12 分) 【问题 2】(13 分) 答案 【问题 1】答案 【问题 2】答案 相关推荐 案例 阅读以下关于软件架构评估的叙述&#xff0c;在答题纸上回答问题 1 和问题 2。 【题目】 某单位为了建设健全的公路桥梁养护管理档案&#xff0c;拟开发一套公…

USB PHY—— PHY 基础

芯片厂商开发了一些 USB PHY 芯片&#xff0c;可以把 DP、DM上的差模信号转成共模信号。 USB PHY 负责最底层的信号转换&#xff0c;作用类似于网口的 PHY。 USB 信号传输前&#xff0c;需要通过 PHY 把 USB 控制器的数字信号转成线缆上的模拟信号。USB 控制器和 PHY 之间的总…

中国严肃游戏开发的最佳实践

严肃游戏产业在中国迅速发展&#xff0c;将娱乐与教育、培训和宣传活动融为一体。旨在实现特定学习成果或行为改变的严肃游戏在从企业培训到医疗保健和教育的各个领域越来越受欢迎。然而&#xff0c;为中国市场开发成功的严肃游戏需要深入了解当地文化、用户偏好和技术趋势。以…

函数栈帧的创建和销毁(VS2022)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、前面的困惑 二、什么是函数栈帧 三、关于函数栈帧的基础知识 1.栈 2.寄存器 2.1 什么是寄存器 2.2 相关的寄存器 2.3 相关汇编命令 2.4 预备知识 四、解析函数…

盘点4款可以免费使用的高效ai PPT制作工具。

平时我们自己制作一个PPT还是需要比较长的时间的&#xff0c;从构思内容&#xff0c;到制作主题和逻辑框架&#xff0c;然后是挑选模板、排版配色等&#xff0c;过程比较繁琐且费时。但是&#xff0c;现在出现了很多的AIPPT制作工具&#xff0c;能够快速的帮助用户生成一个完整…

来自工业界的知识库 RAG(六),独特的 RAG 框架 dsRAG 核心亮点解读

背景介绍 在前面介绍了较多的开源 RAG 框架&#xff0c;比如主打 Rerank 的 QAnything, 主打精细文件解析的 RagFlow, 主打模块化灵活组合的 GoMate。这些库的设计除了少量的独特之处外&#xff0c;相似的部分很多。 最近有注意到一款另类的 RAG 框架 dsRAG&#xff0c;使用了…

openGauss在龙芯平台部署的实践

服务器环境 系统信息 NAME"Loongnix-Server Linux" VERSION"8" ID"loongnix-server" ID_LIKE"rhel fedora centos" VERSION_ID"8" PLATFORM_ID"platform:lns8" PRETTY_NAME"Loongnix-Server Linux 8"…

第四十篇-TeslaP40+Ollama+Ollama-WebUI(自编译)

本文介绍用自己编辑ollama-webui,链接本地ollama 环境 系统&#xff1a;CentOS-7 CPU: 14C28T 内存&#xff1a;32G 显卡&#xff1a;Tesla P40 24G 驱动: 535 CUDA: 12.2 Ollama: 0.3.0本地ollama 参考 [第二十四篇-Ollama-在线安装](https://blog.csdn.net/hai4321/articl…

2024软考:一场与“难”共舞的奇妙冒险,你值得拥有!

在这个时代&#xff0c;如果说有什么考试能让IT界的勇士们闻风丧胆&#xff0c;又爱又恨&#xff0c;那软考绝对能C位出道&#xff0c;成为众多技术大佬心中的“白月光”与“朱砂痣”。随着岁月悠悠&#xff0c;2024年的软考似乎又悄悄地在难度上动了点小心思&#xff0c;让人不…

vue设置水印

水印图例 1.新建Watermark.js 文件 const watermark {}const setWatermark (text, sourceBody) > {const id Math.random() * 10000 - Math.random() * 10000 / Math.random() * 10000if (document.getElementById(id) ! null) {document.body.removeChild(document.getE…

阿里云服务器开放端口的完整版图文教程

原文&#xff1a;阿里云服务器开放端口完整版教程&#xff1a;https://www.yundashi168.com/488.html 笔者近期开发完成的服务端程序部署在阿里云的ECS云服务器上面&#xff0c;一些应用程序配置文件需要设置监听的端口&#xff08;如Tomcat的8080、443端口等&#xff09;&…

萤石云 移动端demo指南

再来一篇&#xff0c;这次是萤石开放平台移动端demo的使用指南 一、Demo使用指南 从官网下载demo&#xff0c;下载地址demo首页如下&#xff1a; 填入对应参数 输入框是否必填解释服务器区域是国内选择Asia-China&#xff0c;海外选择对应的区域。选择后ApiUrl和WebUrl会自动…

餐饮行业eHR人力资源管理系统应该如何选择?

数字化转型与增长成为餐饮企业品牌竞争的创新壁垒&#xff0c;越来越多的餐饮企业&#xff08;门店&#xff09;依托数字化工具和手段&#xff0c;覆盖从内部组织到外部的数字化升级&#xff0c;包括员工管理、营销、客户管理&#xff0c;以及采购供应链等各环节的数字化运营。…

Netty从入门到超神-NIO 三大核心(selector,channel,buffer)(二)

前言 上一篇文章认识了一下Java的三大IO&#xff0c;这一章节我们详细了解一下NIO的工作原理以及三大核心Selector,Channel,Buffer并尝试来做一些小案例。 Java NIO 模型 Java NIO有三个核心的组件&#xff1a; selector 选择器 &#xff0c; channel 通道 &#xff0c; buf…

SpringBoot日常:Spring之@PostConstruct解析

简介 spring的Bean在创建的时候会进行初始化&#xff0c;而初始化过程会解析出PostConstruct注解的方法&#xff0c;并反射调用该方法。 PostConstruct 的使用和特点 只有一个非静态方法能使用此注解&#xff1b;被注解的方法不得有任何参数&#xff1b;被注解的方法返回值必…

Marin说PCB之TP测试的Layout设计要求

提及到TP点这个器件想必诸位道友们肯定不会陌生吧&#xff0c;我们的单板在量产之前都是需要做很多测试的&#xff0c;一般在产品研发的A版本和B版本的时候都是需要在单板上加上这个器件的。小编我最近在做一个改板&#xff0c;项目组为了降本增效&#xff0c;把单板的尺寸缩小…

Git 忽略已经提交的文件

对于未提交过的文件直接用ignore文件即可,不再赘述 对于已经提交过的文件,但是实际上不需要的,可以用git rm --cached命令 比如下图这个 .vsconfig被我误提交了或者忘了在ignore里添加了 但是我实际上不想要这个文件,那么在项目根目录打开git bash ,输入 git rm --cached .vsc…

LMDeploy 量化部署

创建环境和模型 conda create -n lmdeploy python3.10 -y conda activate lmdeploy conda install pytorch2.1.2 torchvision0.16.2 torchaudio2.1.2 pytorch-cuda12.1 -c pytorch -c nvidia -y pip install timm1.0.8 openai1.40.3 lmdeploy[all]0.5.3 mkdir /root/models …

[海思3403] 初始配置

虚拟机和板卡桥接 首先将虚拟机设置为桥接模式 板卡用网线和PC机连接&#xff0c;PC机用VMware打开Ubuntu虚拟机 点击虚拟网络编辑器&#xff0c;点击更改设置