【SpringCloud-9】JWT

news2024/9/22 21:25:15

这一篇主要介绍一下,微服务之间的用户权限问题。 通常呢,对于用户的登录鉴权,有两种方式:

1、基于session的方式:

session是要存到服务端的,但是分布式服务太多,不可能每个服务端都存。  那就只有使用session共享,将sessionId放到cookie中。 但是呢也有问题,现在客户端形式很多,像PC端 h5对cookie的支持还好,移动端有时候对cookie就没法有效使用。 

2、基于token的方式:

token通常包含一些用户信息,生成后加密返给客户端。 服务端可以不用存储,返回给客户端自行存储。  但是呢,一般token都包含较多信息,每个接口都传输,带宽占用多。 另外,token验证都要请求认证中心,压力较大。 

3、基于JWT(JSON Web Token

  • 什么是JWT

JWT本身不是springcloud的东西,只是在微服务中使用起来会很方便。 认证服务按照jwt的格式,颁发授权令牌后,不用存储。JWT令牌中已经包括了⽤户相关的信 息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法⾃⾏完成令牌校验,⽆需每次都请求认证服务完成授权。

  • JWT结构

​​JWT令牌由三部分组成,每部分中间使⽤点(.)分隔,⽐如:xxxxx.yyyyy.zzzzz

  • Header部分​​​​​​​

包含令牌类型和使用的hash算法(如HMAC SHA256或 RSA) ,如:​​​​​​​

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

将上面内容用Base64Url编码,就是JWT的第一部分。 

  • Payload部分

​​​​​​​这部分也是一个json对象,存放有效信息,如 iss(签发者), exp(过期时间戳), sub(⾯向的

⽤户)等,也可⾃定义字段。 此部分不建议存放敏感信息,因为此部分可以解码还原内容。

​​​​​​​{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

第二部分也是使用Base64Url编码。 

  • Signature部分 

签名部分,用于防止JWT内容被篡改。  处理方式是,将第一部分和第二部分的Base64编码字符串,用"."连接,再加上一个自定义的secret,一起使用Header中的签名算法加密。如secret为abc123:

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

得到的结果:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.

Z-quzzUBR0Yyj6B37GElTRVPiHoIAWY4-q9i05aYCA8

这个结果是可以被反解析的,各服务就可以自己解析了。

在springcloud中,如何使用JWT呢。 

比如,在认证服务器登录并颁发JWT令牌给客户端,后续在网关校验令牌,这时候网关无需请求认证服务器,自己可以反解析令牌并校验。 如果通过,继续访问后续服务。

 

项目搭建 示例:

  • JWT管理服务:主要提供创建jwt令牌和反解析令牌的方法,并提供jar包。
  • 认证中心:依赖JWT管理服务的jar包,登录成功后,调用jar包中的方法,创建令牌。
  • 网关:依赖JWT管理服务的jar包,反解析请求中的令牌,进行鉴权。

JWT管理服务:

1、导入jwt的包

        <dependency>
                <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
            <scope>compile</scope>
        </dependency>

2、配置文件:

auth:
  jwt:
    enabled: true   # 是否开启JWT登录认证功能
    secret: abc123  # JWT 私钥,用于校验JWT令牌的合法性
    expiration: 3600000 # JWT 令牌的有效期,一个小时
    header: Authorization # HTTP 请求的 Header 名称,该 Header作为参数传递 JWT 令牌

3、configuration

@Data
@ConfigurationProperties(prefix = "auth.jwt")
@Component
public class AuthJwtProperties {
 
    //是否开启JWT,即注入相关的类对象
    private Boolean enabled = true;
 
    //JWT 密钥
    private String secret;
 
    //accessToken 有效时间
    private Long expiration;
 
    //header名称
    private String header;
 
    //是否使用默认的JWTAuthController
    private Boolean useDefaultController = false;
 
}

 4、核心api

import com.seven.springcloud.config.AuthJwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
 
 
@Component
public class JwtTokenUtil {
 
    private static final String JWT_CACHE_KEY = "jwt:userId:";
    private static final String ACCESS_TOKEN = "access_token";
    private static final String REFRESH_TOKEN = "refresh_token";
    private static final String EXPIRE_IN = "expire_in";
 
    @Resource
    private StringRedisTemplate stringRedisTemplate;
 
    @Resource
    private AuthJwtProperties jwtProperties;
 
    /**
     * 生成 token 令牌主方法
     * @param userId 用户Id或用户名
     * @return 令token牌
     */
 
    public Map<String, Object> generateTokenAndRefreshToken(String userId, String username) {
        //生成令牌及刷新令牌
        Map<String, Object> tokenMap = buildToken(userId, username);
        //redis缓存结果
        cacheToken(userId, tokenMap);
        return tokenMap;
    }
 
    //将token缓存进redis
    private void cacheToken(String userId, Map<String, Object> tokenMap) {
        stringRedisTemplate.opsForHash().put(JWT_CACHE_KEY + userId, ACCESS_TOKEN, tokenMap.get(ACCESS_TOKEN));
        stringRedisTemplate.opsForHash().put(JWT_CACHE_KEY + userId, REFRESH_TOKEN, tokenMap.get(REFRESH_TOKEN));
        stringRedisTemplate.expire(userId, jwtProperties.getExpiration() * 2, TimeUnit.MILLISECONDS);
    }
    //生成令牌
    private Map<String, Object> buildToken(String userId, String username) {
        //生成token令牌
        String accessToken = generateToken(userId, username, null);
        //生成刷新令牌
        String refreshToken = generateRefreshToken(userId, username, null);
        //存储两个令牌及过期时间,返回结果
        HashMap<String, Object> tokenMap = new HashMap<>(2);
        tokenMap.put(ACCESS_TOKEN, accessToken);
        tokenMap.put(REFRESH_TOKEN, refreshToken);
        tokenMap.put(EXPIRE_IN, jwtProperties.getExpiration());
        return tokenMap;
    }
    /**
     * 生成 token 令牌 及 refresh token 令牌
     * @param payloads 令牌中携带的附加信息
     * @return 令牌
     */
    public String generateToken(String userId, String username,
                                Map<String,String> payloads) {
        Map<String, Object> claims = buildClaims(userId, username, payloads);;
 
        return generateToken(claims);
    }
    public String generateRefreshToken(String userId, String username, Map<String,String> payloads) {
        Map<String, Object> claims = buildClaims(userId, username, payloads);
 
        return generateRefreshToken(claims);
    }
    //构建map存储令牌需携带的信息
    private Map<String, Object> buildClaims(String userId, String username, Map<String, String> payloads) {
        int payloadSizes = payloads == null? 0 : payloads.size();
 
        Map<String, Object> claims = new HashMap<>(payloadSizes + 2);
        claims.put("sub", userId);
        claims.put("username", username);
        claims.put("created", new Date());
        //claims.put("roles", "admin");
 
        if(payloadSizes > 0){
            claims.putAll(payloads);
        }
 
        return claims;
    }
 
 
    /**
     * 刷新令牌并生成新令牌
     * 并将新结果缓存进redis
     */
    public Map<String, Object> refreshTokenAndGenerateToken(String userId, String username) {
        Map<String, Object> tokenMap = buildToken(userId, username);
        stringRedisTemplate.delete(JWT_CACHE_KEY + userId);
        cacheToken(userId, tokenMap);
        return tokenMap;
    }
 
    /**
     * 从request获取userid
     * @param request http请求
     * @return request.getHeader
     */
    public String getUserIdFromRequest(HttpServletRequest request) {
        return request.getHeader(USER_ID);
    }
 
    //缓存中删除token
    public boolean removeToken(String userId) {
        return Boolean.TRUE.equals(stringRedisTemplate.delete(JWT_CACHE_KEY + userId));
    }
 
 
    /**
     * 从令牌中获取用户id
     *
     * @param token 令牌
     * @return 用户id
     */
    public String getUserIdFromToken(String token) {
        String userId;
        try {
            Claims claims = getClaimsFromToken(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            userId = null;
        }
        return userId;
    }
    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = (String) claims.get(USER_NAME);
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
 
 
    /**
     * 判断令牌是否不存在 redis 中
     *
     * @param token 刷新令牌
     * @return true=不存在,false=存在
     */
    public Boolean isRefreshTokenNotExistCache(String token) {
        String userId = getUserIdFromToken(token);
        String refreshToken = (String)stringRedisTemplate.opsForHash().get(JWT_CACHE_KEY + userId, REFRESH_TOKEN);
        return refreshToken == null || !refreshToken.equals(token);
    }
 
    /**
     * 判断令牌是否过期
     *
     * @param token 令牌
     * @return true=已过期,false=未过期
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            //验证 JWT 签名失败等同于令牌过期
            return true;
        }
    }
 
    /**
     * 刷新令牌
     *
     * @param token 原令牌
     * @return 新令牌
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }
 
    /**
     * 验证令牌
     *
     * @param token       令牌
     * @param userId  用户Id用户名
     * @return 是否有效
     */
    public Boolean validateToken(String token, String userId) {
 
        String username = getUserIdFromToken(token);
        return (username.equals(userId) && !isTokenExpired(token));
    }
 
 
    /**
     * 生成令牌
     * @param claims 数据声明
     * @return 令牌
     */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis()
                + jwtProperties.getExpiration());
        return Jwts.builder().setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512,
                        jwtProperties.getSecret())
                .compact();
    }
    /**
     * 生成刷新令牌 refreshToken,有效期是令牌的 2 倍
     * @param claims 数据声明
     * @return 令牌
     */
    private String generateRefreshToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * 2);
        return Jwts.builder().setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret())
                .compact();
    }
 
    /**
     * 从令牌中获取数据声明,验证 JWT 签名
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

}

核心api中主要包含了创建jwt,解析jwt,刷新token等方法。 具体的可根据实际场景定制。

认证中心和网关,就没有什么特殊的了。 只需要依赖jwt管理服务的jar包,做jwt令牌的创建颁发,和令牌校验。

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

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

相关文章

Python:文件选择界面 and 文件夹选择界面

文章目录 &#xff08;1&#xff09;文件选择界面&#xff08;2&#xff09;文件夹选择界面 Python本身没有内置的文件夹选择界面。然而&#xff0c;可以使用第三方库来实现在代码中选择文件或文件夹的功能。一个常用的库是tkinter&#xff0c;它是Python的标准GUI库之一&#…

IDEA中侧边栏没有git commit模块,如何恢复?

一、修改之前 侧边栏没有git commit模块 二、修改之后 侧边栏恢复了git commit模块 三、下面是恢复教程 1.中文版 打开 文件 -> 设置 -> 版本控制 -> 提交 -> 勾选 【使用非模式提交界面】 -> 点击【确定】 2.英文版 打开 file -> Settings -> Version Co…

ABB机器人在RobotStudio中进行数字与字符串相互转换的具体方法

ABB机器人在RobotStudio中进行数字与字符串相互转换的具体方法 如下图所示,打开RobotStudio软件,在RAPID—Module1中编写程序,首先声明几个测试需要用到的变量, 本例中利用 NumToStr 函数将数组变量中的元素依次转换成字符后赋值给tempString变量, 如下图所示,利用 StrTo…

第八章 npm锁定版本

1、历史原因 当我们走 npm install 带 ^ 会升级 为什么要锁版本 稳定大于一切 代码需要可控 风险可控 系统可控 环境需要一致

第四章 React18的重要更新和使用的新特性

1、7个新特性 2、3个新API 3、1个新模式 4、2个新并发API 1、7个新特性 Render API 使用了它才可以进入并发模式的渲染 setState自动批处理 有些情况&#xff0c;不希望合并处理 flsuhSync 关于卸载组件时的更新状态警告&#xff08;直接删除这个报错&#xff09; 关于r…

nacos启动问题整理

一 win下启动 1、nacos1.X启动 2、nacos2.X启动 2.x需要jdk11以上版本 1&#xff09;打开bin目录&#xff0c;修改startup.com脚本 启动模式&#xff0c;点击startup.cmd默认启动的集群模式&#xff0c;需要修改这个启动文件 2&#xff09; 连接mysql&#xff0c;执行sql…

【笔记】数字电路基础2 - 数制编码与逻辑电路

目录 数制、编码与逻辑代数数制编码逻辑代数 组合逻辑电路组合逻辑电路分析与设计编码器译码器加法器数值比较器数据选择器奇偶校验器 数制、编码与逻辑代数 数制 本小节主要陈述十进制、二进制、十六进制及其对应的转换法则&#xff0c;网上对应的文章已经有很多&#xff0c;…

一文搞定SpringBoot中日志框架使用

文章目录 Spring Boot 对日志框架的封装SLF4J Logback快速入门调试模式Logback 扩展SLF4J Log4J2 Spring Boot 对日志框架的封装 我们知道在日志方面&#xff0c;SpringBoot默认是使用的SLF4JLogBack的形式。我们来看看它使用的日志实现框架LogBack&#xff0c;其在 Default…

Tomcat、Maven以及Servlet的基本使用

Tomcat什么是TomcatTomcat的目录结构启动Tomcat MavenMaven依赖管理流程配置镜像源 Servlet主要工作实现Servlet添加依赖实现打包分析 配置插件 Tomcat 什么是Tomcat Tomcat 是一个 HTTP 服务器。前面我们已经学习了 HTTP 协议, 知道了 HTTP 协议就是 HTTP 客户端和 HTTP 服务…

LinearAlgebraMIT_4_矩阵的LU分解

矩阵做逆变换需要要反过来&#xff0c;如下&#xff0c; 转置的逆等于逆的转置。 在知道了上面的基础知识后&#xff0c;我们进行矩阵的分解&#xff0c;常见如LU分解和LDU分解&#xff0c;如下&#xff0c; 在这里&#xff0c;我们首先具有一个矩阵A&#xff0c;我们对矩阵A进…

用html+javascript打造公文一键排版系统3:获取参数设置、公文标题排版

我们用自定义函数setDocFmt()来实现对公文的排版。 一、获取公文参数值 要对公文进行排版&#xff0c;首先要读取公文“参数设置”区中的参数值。比如公文要求对公文标题的一般规定是&#xff1a;一般用2号小标宋体字&#xff0c;居中显示。标题与正文中间空一行。 这些是“参…

Git---企业级开发模型

文章目录 前言拓展 一、系统开发环境二、Git分支设计规范master分支release分支develop分支feature分支hotfix分支 三、企业级项目管理实战准备工作创建项目创建仓库添加成员1. 添加企业成员2.添加项目成员3. 添加仓库开发⼈员 开发场景-基于git flow模型的实践新需求加入修复测…

Python之字典(dict)基础知识点

文章目录 一、创建字典1.1 基于dict函数创建1.2 基于{}创建1.3 基于空字典添加元素创建 二、访问字典三、修改字典四、删除字典五、字典的常用方法5.1 dict.items()方法5.2 dict.get()方法5.3 dict.setdefault()方法 参考资料 字典是python当中的一种数据类型&#xff0c;其结果…

Kafka入门,手动提交offset,同步提交,异步提交,指定 Offset 消费(二十三)

手动提交offset 虽然offset十分遍历&#xff0c;但是由于其是基于时间提交的&#xff0c;开发人员难以把握offset提交的实际。因此Kafka还提供了手动提交offset的API 手动提交offset的方法有两种&#xff1a;分别commitSync(同步提交)和commitAsync(异步提交)。两者的相同点是&…

conda创建环境等相关知识

1、首先下载Anaconda&#xff0c;官网下载即可&#xff0c;打开如下选项&#xff0c; 2、创建一个环境&#xff1a;命令如下 conda create -n 虚拟环境名称 python?实例&#xff1a;创建一个pytorch环境&#xff0c;指定python版本为3.9版本 conda create -n pytorch pytho…

【七天入门数据库】第一天 MySQL的安装部署

系列文章传送门&#xff1a; 【七天入门数据库】第一天 MySQL的安装部署 【七天入门数据库】第二天 数据库理论基础 【七天入门数据库】第三天 MySQL的库表操作 MySQL数据库存在多种版本&#xff0c;不同的版本在不同的平台上&#xff08;OS&#xff0c;也就是操作系统上&a…

文件资源管理器卡住,使用任务管理器结束任务后桌面图标和任务栏消失的解决方案

事情的起因是这样的&#xff0c;我想删除压缩包里的一张照片&#xff0c;结果文件资源管理器就卡住了&#xff0c;删除进度一直是0%&#xff0c;等了好久也没反应。没办法&#xff0c;只能掏出秘密武器任务管理器了&#xff0c;找到文件资源管理器&#xff0c;右键选择结束任务…

游戏渲染技术:前向渲染 vs 延迟渲染 vs Forward+渲染 (一)

在这篇文章中&#xff0c;会分析和对比三种渲染算法&#xff1a; 前向渲染(Forward Rendering)延迟着色(Deferred Shading)Forward(基于Tile的前后渲染) 介绍 前向渲染 前向渲染是通过在场景中光栅化每个几何对象来工作的&#xff0c;在着色过程中&#xff0c;通过迭代每个灯…

ModaHub魔搭社区:向量数据库Zilliz Cloud删除 Entity和删除 Collection教程

目录 删除单个 Entity 批量删除 Entity 开始前 操作步骤 使用限制 Entity 是指存储在 Zilliz Cloud 集群中的数据实体,包含用于处理、搜索和查询的数据。如果您不再需要某个 Entity,可以执行相关操作将其删除。 本文介绍如何从 Collection 中删除单个或多个 Entity。 …

RocketMQ5.0消息消费<三> _ 消息消费

RocketMQ5.0.0消息消费&#xff1c;三&#xff1e; _ 消息消费 一、消息消费 1. 消费UML图 PUSH模式消息拉取机制参考《RocketMQ5.0.0消息消费&#xff1c;一&#xff1e; _ PUSH模式的消息拉取​》&#xff0c;PullMessageService负责对消息队列进行消息拉取&#xff0c;从B…