JWT的深入理解

news2025/1/6 19:18:37

1、JWT是什么

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在不同实体之间安全地传输信息。它由三部分组成,即头部(Header)、载荷(Payload)和签名(Signature)。以下是JWT的基本概念和使用方式:

  1. 头部(Header):头部通常由两部分组成,算法类型和令牌类型。例如:

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    

    在上述示例中,alg表示使用的签名算法,typ表示令牌的类型。

  2. 载荷(Payload):载荷包含需要传输的信息,可以自定义添加一些标准或私有的声明。例如:

    {
      "sub": "user123",
      "name": "John Doe",
      "role": "admin",
      "exp": 1625102873
    }
    

    在上述示例中,sub表示主题(subject),name表示名称,role表示角色,exp表示过期时间。

  3. 签名(Signature):签名用于验证令牌的真实性和完整性。它是对头部和载荷进行签名的结果,使用私钥进行签名。例如:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret
    )
    

    在上述示例中,通过对头部和载荷进行签名,使用一个秘密密钥(secret)生成签名。

使用JWT的基本流程如下:

  1. 用户认证:用户向服务器发送认证请求,服务器验证用户的身份和凭证。

  2. 生成JWT:服务器根据用户的身份信息生成JWT,并将其返回给客户端。

  3. 客户端存储JWT:客户端将收到的JWT保存在本地,通常使用cookie或本地存储(如localStorage)。

  4. 后续请求:客户端在每次请求中将JWT作为身份凭证附加到请求的头部(通常是Authorization头)。

  5. 服务器验证:服务器在接收到请求时,解析JWT并验证其真实性和有效性。

  6. 响应请求:服务器根据JWT中的信息进行相应的操作,并返回相应的响应结果。

JWT的优势在于它是自包含的,即令牌本身携带了用户信息和验证信息,减少了对服务器端存储和查找用户信息的开销。同时,JWT可以在跨域环境中使用,具有可扩展性和灵活性。

需要注意的是,为了保证安全性,JWT的签名部分应该使用安全的密钥进行签名,并且需要进行合适的过期时间设置和刷新机制来保护令牌的安全性。

2、JWT流程

JWT流程图

在这个过程中,JWT令牌作为身份凭证被生成并在客户端和服务器之间传递。服务器验证JWT的签名和有效性,并从中提取用户的身份信息来进行鉴权和授权操作。JWT的特点是自包含的,减少了服务器端的存储和查找开销,并提供了无状态的身份验证机制。

需要注意的是,为了保证JWT的安全性,应使用安全的密钥对JWT进行签名,并根据需求设置适当的过期时间和刷新机制,以保护令牌的安全性。

3、实操代码

登录的接口实现,伪代码:

public UserVo login(String account, String password, boolean isRememberMe, HttpServletResponse response) {
		
		// 数据库用户校验
        User dbUser = userMapper.selectOne(new QueryWrapper<User>().eq(User.ACCOUNT, account));
        if (dbUser == null) {
            throw new GlobalException(BaseResultEnum.USER_NOT_EXISTS);
        }
        checkUser(dbUser);
        checkPassword(dbUser, password);
        
		// 角色校验
        Long userId = dbUser.getId();
        List<Role> dbRoles = getRoles(userId);
        if (CollectionUtil.isEmpty(dbRoles)) {
            throw new GlobalException(BaseResultEnum.NO_AUTHORITY_ROLE);
        }
        
		// 权限校验
        Set<Long> roleIDs = dbRoles.stream().map(Role::getId).collect(Collectors.toSet());
        Set<Permission> dbPermission = getPermissions(roleIDs);
        if (CollectionUtil.isEmpty(dbPermission)) {
            throw new GlobalException(BaseResultEnum.NO_AUTHORITY_ROLE);
        }
        
		// 封装用户信息
        AuthUser authUser = getAuthUser(dbUser, userId, dbPermission, roles, LoginSourceType.PC.getName());
        authUser.setLoginType(LoginSourceType.PC.getType());
        // 获取用户设置的有效时间
        authUser.setExpiredTime(User.getValidTimeByType(authUser.getValidTimeType()));
        // 生成令牌
        String accessKey = UUIDUtils.getUUID();
        // 缓存令牌与信息
        cacheToken(authUser, accessKey, LoginSourceType.PC.getType());
        
        // header存储accessKey,返回JWT令牌
        response.addHeader(ShiroConstants.TOKEN_NAME_FOR_HTTP_HEADER, accessKey);
        // 是否记住密码
        if (isRememberMe) {
            addRememberMeCookie(account, password, response);
        }
        
        // 返回的用户信息,用于Cookie保存
        UserVo userVo = getUserVo(dbUser);
        Set<String> perms = dbPermission.stream().map(Permission::getCode).collect(Collectors.toSet());
        userVo.setPerms(perms);
        userVo.setToken(accessKey);

        return userVo;
    }

结果情况如下:

生成的JWT令牌在每次请求中都需携带令牌。
在这里插入图片描述

Shiro、JWT的拦截

定义一个拦截器类,继承 BasicHttpAuthenticationFilter 类。 BasicHttpAuthenticationFilter 类又继承情况如下:最终实现了Filter与继承自ServletContextSuper。
在这里插入图片描述
重写BasicHttpAuthenticationFilter 类的 preHandle()->isAccessAllowed()->isLoginAttempt()->executeLogin() 方法。

使用shiro组件

配置Shiro的配置文件,注册ShiroFilterFactoryBean(),

@Bean("shiroFilter")
    public ShiroFilterFactoryBean factory() {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager());
        factoryBean.setUnauthorizedUrl("/noLogin");
        // 用LinkedHashMap添加拦截的uri,其中authc指定需要认证的uri,anon指定排除认证的uri
        LinkedHashMap<String, String> filterRuleMap = new LinkedHashMap<>();
        filterRuleMap.put("/login", "anon");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;

}

/**
DefaultWebSecurityManager类主要定义了设置subjectDao,获取会话模式,设置会话模式,设置会话管理器,是否是http会话模式等操作,它继承了DefaultSecurityManager类,实现了WebSecurityManager接口
*/
@Bean("securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(jwtRealm);
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);
        return manager;
    }

参考地址:http://shiro.apache.org/web.html#urls-
shiro:DefaultWebSecurityManager详解

Shiro的认证与授权

定义一个配置JWTRealm类,继承AuthorizingRealm类
AuthorizingRealm类

   @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        AuthUser authUser = (AuthUser) principals.getPrimaryPrincipal();

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        if (authUser == null) {
            return simpleAuthorizationInfo;
        }
        Set<String> perms = authUser.getPerms();
        if (CollUtil.isNotEmpty(perms)) {
            simpleAuthorizationInfo.addStringPermissions(perms);
        }
        Set<String> roles = authUser.getRoles();
        if (CollUtil.isNotEmpty(roles)) {
            simpleAuthorizationInfo.addRoles(roles);
        }
        return simpleAuthorizationInfo;
    }

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String accessKey = (String) auth.getCredentials();
        if (StrUtil.isEmpty(accessKey)) {
            throw new AuthenticationException(BaseResultEnum.NON_LOGIN.getMessage());
        }
        String userString = stringRedisTemplate.opsForValue().get(String.format(RedisKey.USER_TOKEN_KEY, accessKey));
        if (StrUtil.isEmpty(userString)) {
            throw new AuthenticationException(BaseResultEnum.TOKEN_EXPIRED.getMessage());
        }

        AuthUser user = JSON.parseObject(userString, AuthUser.class);
        if (user == null) {
            throw new AuthenticationException(BaseResultEnum.TOKEN_EXPIRED.getMessage());
        }
        // 判断是否登陆过
/*        String userTokenMappingKey = String.format(RedisKey.USER_TOKEN_MAPPING_KEY, user.getId(), LoginSourceType.PC.getType());
        //用户关联的token
        String lastAccessKey = stringRedisTemplate.opsForValue().get(userTokenMappingKey);
        if (StrUtil.isEmpty(lastAccessKey)||!lastAccessKey.equals(accessKey)) {
            throw new AuthenticationException(BaseResultEnum.TOKEN_EXPIRED.getMessage());
        }*/
/*       
        //刷新当前token过期时间
        stringRedisTemplate.expire(String.format(RedisKey.USER_TOKEN_KEY, accessKey), user.getExpiredTime(), TimeUnit.MILLISECONDS);*/
        return new SimpleAuthenticationInfo(user, accessKey, getName());
    }

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

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

相关文章

获取QT界面坐标的各种方法

链接 ract() 获取rect所在部件的尺寸。 rect()返回的QRect对象可以用来做什么

openResty的Redis模块踩坑记录

OpenResty提供了操作Redis的模块&#xff0c;我们只要引入该模块就能直接使用。说是这样说&#xff0c;但是实践起来好像并不太顺利。 1.设置了密码的redis&#xff0c;lua业务逻辑中需要添加身份认证代码 网上很多资料、文章似乎都是没有设置redis密码&#xff0c;说来也奇怪…

JS区域滤镜

思路 简单一点的&#xff0c;像素点X坐标小于图宽1/3和大于2/3的点变灰&#xff0c;中间的点不变。 复杂的暂时不会搞。 原图 处理后 <html> <style> #canvas { width:100%; } </style> <body> <input id"file" type"file" …

python中的生成器(generator)

一、生成器 生成器是 Python 中非常有用的一种数据类型&#xff0c;它可以让你在 Python 中更加高效地处理大量数据。生成器可以让你一次生成一个值&#xff0c;而不是一次生成一个序列&#xff0c;这样可以节省内存并提高性能 二、实现generator的两种方式 python中的gener…

SAP从放弃到入门系列之WIP Batch(Work-in-Process ) -Part1

目录 一、 概述二、 系统配置三、 数据设置最后 ERP系统的复杂性并不单是架构设计和技术造成的&#xff0c;而是它所要支撑的业务场景&#xff0c;涉及行业越广泛越复杂软件功能越复杂&#xff0c;复杂的背后是业务实践沉淀和优化的流程。平时看着部分系统功能很复杂&#xff0…

47.判断类关键字 if else switch case default

目录 1 if 2 else 3 判断的嵌套 4 switch,case,default 4.1 基本使用 4.2 需要注意的点 1 if if后面的括号加表达式的内容&#xff0c;大括号中加入 条件为true 时要运行的代码 经测试如果我们将a的值设置为0&#xff0c;则不会弹出警告框 2 else 和if配合使用…

ubuntu netplan工具原理(网络配置、ip修改ip、固定ip)(NetworkManager)

https://netplan.io/ 文章目录 netplan工作原理netplan -h原翻译命令释义- help&#xff1a;显示netplan的帮助消息。- apply&#xff1a;将当前netplan配置应用到运行系统。示例命令&#xff1a;netplan apply --debug- generate&#xff1a;从/etc/netplan/*.yaml生成特定于后…

iOS开发 - NotificationService语音播报

iOS NotificationService语音播报 最近碰到个接收到推送要实现语音播报的需求&#xff0c;需要后台推送通知&#xff0c;APP客户端收到通知之后语音播放&#xff1a;“您的账户收到一笔巨款”的功能。 因为工程之前已经集成了极光推送服务。这里直接使用Notification Service…

【科研绘图】MacOS系统OmniGraffle实用指南

用过不少绘图软件&#xff0c;包括Visio (only for Windows)、ProcessOn、draw.io等主流软件&#xff0c;然后换Mac后尝试了实验室在用的OmniGraffle&#xff0c;才第一次感受到了绘图软件的人性化和强大&#xff01; 实用操作总结 按住Shift后调整元素位置或调整线段&#x…

使用STM32 再实现感应开关盖垃圾桶

硬件介绍 SG90舵机 如上图所示的舵机SG90&#xff0c;橙线对应PWM信号&#xff0c;而PWM波的频率不能太高&#xff0c;大约50Hz&#xff0c;即周期0.02s&#xff0c;20ms左右。 在20ms的周期内&#xff0c;高电平占多少秒和舵机转到多少度的关系如下&#xff1a; 0.5ms-----0度…

性能测试持续学习 Docker 新建镜像,启动 POD

目录 前言&#xff1a; 1、构建镜像 2、使用已有镜像启动 Pod 前言&#xff1a; 在进行性能测试时&#xff0c;持续学习Docker的使用可以帮助测试团队更好地管理测试环境和资源。通过使用Docker&#xff0c;可以轻松创建和管理测试环境的镜像&#xff0c;并通过启动POD来快…

win32汇编资源编译RC2103错误 - end of file in string literal

现在有如下的一个资源rc文件&#xff0c; #include <resource.h>#define DLG_MAIN 1 #define IDC_COUNT 101DLG_MAIN DIALOG 50, 50, 113, 40 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "例子" FONT 9, "宋体&…

什么是端口号【图解TCP/IP(笔记十一)】

文章目录 端口号端口号定义根据端口号识别应用通过IP地址、端口号、协议号进行通信识别端口号如何确定端口号与协议 TCP具有代表性的知名端口号UDP具有代表性的知名端口号 端口号 端口号定义 数据链路和IP中的地址&#xff0c;分别指的是MAC地址和IP地址。前者用来识别同一链…

【论文笔记】Guided Skill Learning and Abstraction for Long-Horizon Manipulation

【论文笔记】Guided Skill Learning and Abstraction for Long-Horizon Manipulation 更多笔记&#xff08;在耕&#xff09;&#xff1a;这里 文章目录 【论文笔记】Guided Skill Learning and Abstraction for Long-Horizon ManipulationAbstractI. INTRODUCTIONII. RELATED…

flashFXP 提示: 数据 Socket 错误: 连接已超时 阿里云 安全组

flashFXP 提示: 数据 Socket 错误: 连接已超时的解决办法, 公司搬家后,ip换了.ftp进不去了.当然要查一下服务器防火墙,ftp软件上的端口是否开放.比如自定义的端口为21221,则需要在安全组中开放这个端口.但发现没问题. 同时重新修改了ftp用户的密码.发现也无效 网上有人说,传输模…

MySQL每日一练:多表查询——连接查询、子查询

目录 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; emp表&#xff1a; 2、插入数据&#xff1a; dept表&#xff1a; emp表&#xff1a; 3、 按条件查找 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; create table dept (…

量子纠缠:超越时空的连接

亲爱的读者&#xff0c; 欢迎回到量子力学系列文章。在前几篇文章中&#xff0c;我们介绍了量子力学的起源、基本概念&#xff0c;以及叠加态和超级定位的奇特现象。今天&#xff0c;我们将探索量子力学中最为神奇和令人惊叹的现象之一&#xff1a;量子纠缠。 量子纠缠是一种特…

opencv读取图像数据并修改通道转变内存连续

opencv读取图像数据并修改通道转变内存连续

试题小结3

项目和项目之间的通信 两个java项目&#xff0c;他们之间进行信息的通信 前提&#xff1a;必须知道要通信的java项目&#xff08;接收请求方&#xff09;的服务器的IP地址和访问路径。 其实两个java项目之间的通信还是使用HTTP的请求。主要有两种方式&#xff1a; ①使用ap…

Flask+Echarts搭建全国疫情可视化大屏

FlaskEcharts搭建全国疫情可视化大屏 1、前言2、实现2.1 搭建flask应用2.2 编写html及其对应css代码2.3 可视化展示2.3.1 左上角板块2.3.2 中间上方板块2.3.3 右上角板块2.3.4 左下角板块2.3.5 中间下方板块2.3.6 右下角板块 2.4 完整代码&数据集获取 3、号外 1、前言 本项…