微服务JWT的介绍与使用

1. 无状态登录

1.1 微服务的状态

​ 微服务集群中的每个服务,对外提供的都是Rest风格的接口,而Rest风格的一个最重要的规范就是:服务的无状态性。

​ 什么是无状态?

  1. 服务端不保存任何客户端请求者信息
  2. 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

​ 无状态,在微服务开放中,优势是?

  1. 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
  2. 服务端的是否集群对客户端透明
  3. 服务端可以任意的迁移和伸缩
  4. 减小服务端存储压力

1.2 无状态登录实现原理

在这里插入图片描述

服务器端生产唯一标识(注意:最终需要进行校验)

方案1:UUID,数据单一,不能包含种类过多的信息。

方案2:JWT 生成唯一标识,数据可以自定义。【使用】

为了保证JWT生成数据安全性,采用RSA加密。

浏览器存储和自动携带数据

方案1:使用cookie,有很多局限性(大小,个数)。

方案2:请求参数,get请求URL有长度限制,每一个路径都需要处理比较麻烦。

方案3:浏览器localStorage/sessionStorage存储,通过ajax的请求头携带。【使用】

在这里插入图片描述

1.3 RSA加密

1.3.1 概述

​ RSA公开密钥密码体制是一种使用不同的加密密钥与解密密钥,“由已知加密密钥推导出解密密钥在计算上是不可逆的”密码体制。

​ 在公开密钥密码体制中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK [2]。

​ RSA加密:非对称加密。

​ 同时生产一对秘钥:公钥和私钥。

​ 公钥秘钥:用于加密

​ 私钥秘钥:用于解密

​ 既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;同理,既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出私钥负责签名,公钥负责验证。

使用RSA加密保证token数据在传输过程中不会被篡改。

1.3.2 工具类

package com.czxy.utils;import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/*** Created by liangtong.*/
public class RsaUtils {/*** 从文件中读取公钥** @param filename 公钥保存路径,相对于classpath* @return 公钥对象* @throws Exception*/public static PublicKey getPublicKey(String filename) throws Exception {byte[] bytes = readFile(filename);return getPublicKey(bytes);}/*** 从文件中读取密钥** @param filename 私钥保存路径,相对于classpath* @return 私钥对象* @throws Exception*/public static PrivateKey getPrivateKey(String filename) throws Exception {byte[] bytes = readFile(filename);return getPrivateKey(bytes);}/*** 获取公钥** @param bytes 公钥的字节形式* @return* @throws Exception*/public static PublicKey getPublicKey(byte[] bytes) throws Exception {X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);KeyFactory factory = KeyFactory.getInstance("RSA");return factory.generatePublic(spec);}/*** 获取密钥** @param bytes 私钥的字节形式* @return* @throws Exception*/public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);KeyFactory factory = KeyFactory.getInstance("RSA");return factory.generatePrivate(spec);}/*** 根据密文,生存rsa公钥和私钥,并写入指定文件** @param publicKeyFilename  公钥文件路径* @param privateKeyFilename 私钥文件路径* @param secret             生成密钥的密文* @throws Exception*/public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");SecureRandom secureRandom = new SecureRandom(secret.getBytes());keyPairGenerator.initialize(1024, secureRandom);KeyPair keyPair = keyPairGenerator.genKeyPair();// 获取公钥并写出byte[] publicKeyBytes = keyPair.getPublic().getEncoded();writeFile(publicKeyFilename, publicKeyBytes);// 获取私钥并写出byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();writeFile(privateKeyFilename, privateKeyBytes);}private static byte[] readFile(String fileName) throws Exception {return Files.readAllBytes(new File(fileName).toPath());}private static void writeFile(String destPath, byte[] bytes) throws IOException {File dest = new File(destPath);//创建父文件夹if(!dest.getParentFile().exists()){dest.getParentFile().mkdirs();}//创建需要的文件if (!dest.exists()) {dest.createNewFile();}Files.write(dest.toPath(), bytes);}
}

1.3.3 生产公钥和私钥

package com.czxy.utils;import org.junit.Test;import java.security.PrivateKey;
import java.security.PublicKey;/*** Created by liangtong.*/
public class TestRsa {private static final String pubKeyPath = "D:\\rsa\\rsa.pub";private static final String priKeyPath = "D:\\rsa\\rsa.pri";@Testpublic void testRas() throws Exception {//生产公钥和私钥RsaUtils.generateKey(pubKeyPath, priKeyPath, "234");}@Testpublic void testGetRas() throws Exception {//获得公钥和私钥PublicKey publicKey = RasUtils.getPublicKey(pubKeyPath);PrivateKey privateKey = RasUtils.getPrivateKey(priKeyPath);System.out.println(publicKey.toString());System.out.println(privateKey.toString());}

1.4 JWT

1.4.1 概述

​ JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

1.4.2 添加坐标

<properties><jwt.jjwt.version>0.9.0</jwt.jjwt.version><jwt.joda.version>2.9.7</jwt.joda.version><lombok.version>1.16.20</lombok.version><beanutils.version>1.9.3</beanutils.version>
</properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>${beanutils.version}</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.jjwt.version}</version></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>${jwt.joda.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><scope>provided</scope></dependency></dependencies>

1.4.3 时间处理工具:DateTime

//当前时间
DateTime.now().toDate().toLocaleString()
//当前时间加5分钟
DateTime.now().plusMinutes(5).toDate().toLocaleString()
//当前时间减5分钟
DateTime.now().minusMinutes(5).toDate().toLocaleString()

1.4.4 测试JWT

生成Token
@Test
public void testGenerateToken() throws Exception {String str = Jwts.builder().claim("test","测试数据").setExpiration(DateTime.now().plusMinutes(60).toDate()).signWith(SignatureAlgorithm.RS256,RsaUtils.getPrivateKey(priKeyPath)).compact();System.out.println(str);
}
解析Token
@Test
public void testParseToken() throws Exception {String token = "";Claims claims = Jwts.parser().setSigningKey(RsaUtils.getPublicKey(pubKeyPath)).parseClaimsJws(token).getBody();String text = claims.get("test",String.class);System.out.println(text);
}

1.4.5 工具类:JwtUtils

package com.czxy.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.beanutils.BeanUtils;
import org.joda.time.DateTime;import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.security.PrivateKey;
import java.security.PublicKey;/*** Created by liangtong.*/
public class JwtUtils {/***  私钥加密token* @param data 需要加密的数据(载荷内容)* @param expireMinutes 过期时间,单位:分钟* @param privateKey 私钥* @return*/public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception {//1 获得jwt构建对象JwtBuilder jwtBuilder = Jwts.builder();//2 设置数据if( data == null ) {throw new RuntimeException("数据不能为空");}BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {// 获得属性名String name = propertyDescriptor.getName();// 获得属性值Object value = propertyDescriptor.getReadMethod().invoke(data);if(value != null) {jwtBuilder.claim(name,value);}}//3 设置过期时间jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());//4 设置加密jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);//5 构建return jwtBuilder.compact();}/*** 通过公钥解析token* @param token 需要解析的数据* @param publicKey 公钥* @param beanClass 封装的JavaBean* @return* @throws Exception*/public static <T> T  getObjectFromToken(String token, PublicKey publicKey,Class<T> beanClass) throws Exception {//1 获得解析后内容Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();//2 将内容封装到对象JavaBeanT bean = beanClass.newInstance();BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {// 获得属性名String name = propertyDescriptor.getName();// 通过属性名,获得对应解析的数据Object value = body.get(name);if(value != null) {// 将获得的数据封装到对应的JavaBean中BeanUtils.setProperty(bean,name,value);}}return bean;}
}

1.4.6 生产token和校验token

编写测试对象UserInfo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {private Long id;private String username;
}
编写测试类
package com.czxy.utils;import com.czxy.entity.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import org.junit.Test;/*** Created by liangtong.*/
public class TestJWT {private static final String pubKeyPath = "D:\\rsa\\rsa.pub";private static final String priKeyPath = "D:\\rsa\\rsa.pri";@Testpublic void testToken() throws Exception {UserInfo userInfo = new UserInfo();userInfo.setId(10L);userInfo.setUsername("用户名");String token = JwtUtils.generateToken(userInfo, 30, RsaUtils.getPrivateKey(priKeyPath));System.out.println(token);}@Testpublic void testParserToken() throws Exception {String token = "eyJhbGciOiJSUzI1NiJ9.eyJjbGFzcyI6ImNvbS5jenh5LmVudGl0eS5Vc2VySW5mbyIsImlkIjoxMCwidXNlcm5hbWUiOiLnlKjmiLflkI0iLCJleHAiOjE1NzU5MTYyMDl9.W3Q3Iz1vGq1nf7RQW3eAzkMvkME9P5_5zoDcFQXX0eke07lA2PLuZGCYcB6-DI0i7UrahFOmB0OFQodrK_3CZkrh-sI_802twkGatRaI0ifetRLV_1XHVl_Lymh6SaDdBB1OT3-EQCAppjoHFb9Tyq1EGyQZ5xoU-vLp7fzNQLQ";UserInfo userInfo = JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(pubKeyPath), UserInfo.class);System.out.println(userInfo);}
}

1.4.7 扩展:JWT token组成

JWT的token包含三部分数据:头部、载荷、签名。

名称描述组成部分
头部(Header)通常头部有两部分信息1. 声明类型,这里是JW2. 加密算法,自定义
载荷(Payload)就是有效数据1. 用户身份信息2. 注册声明
签名(Signature)整个数据的认证信息一般根据前两步的数据,再加上服务的的密钥(secret),通过加密算法生成。用于验证整个数据完整和可靠性
生成的数据格式:

在这里插入图片描述

1.5 Zuul整合JWT

1.5.1 自定义配置内容

修改application.yml 添加内容
#自定义内容
sc:jwt:secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥(自定义内容)pubKeyPath: D:/rsa/rsa.pub # 公钥地址priKeyPath: D:/rsa/rsa.pri # 私钥地址expire: 360 # 过期时间,单位分钟filter:allowPaths:- /checkusername- /checkmobile- /sms- /register- /login- /verifycode- /categorys- /news- /brands- /sku/esData- /specifications- /search- /goods- /comments- /pay/callback

1.5.2 JWT配置类

package com.czxy.changgou3.config;import com.czxy.utils.RasUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;/*** Created by liangtong.*/
@Data
@ConfigurationProperties(prefix = "sc.jwt")
@Component
public class JwtProperties {private String secret; // 密钥private String pubKeyPath;// 公钥private String priKeyPath;// 私钥private int expire;// token过期时间private PublicKey publicKey; // 公钥private PrivateKey privateKey; // 私钥private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);@PostConstructpublic void init(){try {File pubFile = new File(this.pubKeyPath);File priFile = new File(this.priKeyPath);if( !pubFile.exists() || !priFile.exists()){RasUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);}this.publicKey = RasUtils.getPublicKey( this.pubKeyPath );this.privateKey = RasUtils.getPrivateKey( this.priKeyPath );} catch (Exception e) {e.printStackTrace();}}}

1.5.3 过滤路径配置类

package com.czxy.changgou3.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.List;/*** Created by liangtong.*/
@Data
@ConfigurationProperties(prefix="sc.filter")
public class FilterProperties {//允许访问路径集合private List<String> allowPaths;
}

1.5.4 过滤器

package com.czxy.changgou3.filter;import com.czxy.changgou3.config.FilterProperties;
import com.czxy.changgou3.config.JwtProperties;
import com.czxy.changgou3.pojo.User;
import com.czxy.utils.JwtUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;/*** Created by liangtong.*/
@Component
//2.1 加载JWT配置类
@EnableConfigurationProperties({JwtProperties.class , FilterProperties.class} )
public class LoginFilter  extends ZuulFilter {//2.2 注入jwt配置类实例@Resourceprivate JwtProperties jwtProperties;@Resourceprivate FilterProperties filterProperties;@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return 5;}@Overridepublic boolean shouldFilter() {     //03.当前过滤器是否执行,true执行,false不执行//3.1 获得用户请求路径// 3.1.1 获得上下文RequestContext currentContext = RequestContext.getCurrentContext();// 3.1.2 获得requestHttpServletRequest request = currentContext.getRequest();// 3.1.2 获得请求路径  , /v3/cgwebservice/loginString requestURI = request.getRequestURI();//3.2 如果路径是 /auth-service/login ,当前拦截不执行for (String path  : filterProperties.getAllowPaths()) {//判断结尾if(requestURI.contains(path)){return false;}}//3.3 其他都执行 run()方法return true;}@Overridepublic Object run() throws ZuulException {//1 获得token//1.1 获得上下文RequestContext currentContext = RequestContext.getCurrentContext();//1.2 获得request对象HttpServletRequest request = currentContext.getRequest();//1.3 获得指定请求头的值String token = request.getHeader("Authorization");//2 校验token -- 使用JWT工具类进行解析// 2.3 使用工具类,通过公钥获得对应信息try {JwtUtils.getObjectFromToken(token , jwtProperties.getPublicKey() , User.class);} catch (Exception e) {// 2.4 如果有异常--没有登录(没有权限)currentContext.addOriginResponseHeader("content-type","text/html;charset=UTF-8");currentContext.addZuulResponseHeader("content-type","text/html;charset=UTF-8");currentContext.setResponseStatusCode( 403 );        //响应的状态码:403currentContext.setResponseBody("token失效或无效");currentContext.setSendZuulResponse( false );        //没有响应内容}// 2.5 如果没有异常,登录了--放行return null;}
}

1.6 下游服务获得token

在这里插入图片描述

zuul默认配置
zuul:sensitive-headers: Cookie,Set-Cookie,Authorization

在这里插入图片描述

修改zuul配置,及删除配置

在这里插入图片描述

其他服务中,可以通过request获得内容

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学建模竞赛实战-Latex公式、表格、图文排版

公式排版 Latex公式排版 行内公式:使用$$包围: 整行公式:使用$$$$包围: 公式编号: 使用$$不能自动编号: 公式编号使用equation: <

分布式 session

分布式 session 种 session 的时候需要注意范围&#xff0c;也就是 cookie.domain。 比如两个域名&#xff1a;a.heo.com&#xff0c;b.heo.com。如果要共享 cookie&#xff0c;可以种一个更高层的公共域名&#xff0c;比如 heo.com。 当服务器 A &#xff08;localhost:808…

算法优化:LeetCode第122场双周赛解题策略与技巧

接下来会以刷常规题为主 &#xff0c;周赛的难题想要独立做出来还是有一定难度的&#xff0c;需要消耗大量时间 比赛地址 3011. 判断一个数组是否可以变为有序 public class Solution {public int minimumCost(int[] nums) {if (nums.length < 3) {// 数组长度小于3时&a…

亚马逊、速卖通、虾皮、Lazada等跨境电商自养号测评的优势

作为一名跨境卖家&#xff0c;深知测评在提升产品排名、权重和销量方面的重要性。然而&#xff0c;随着测评需求的不断增长&#xff0c;寻求测评的过程中也充满了挑战。为了规避风险&#xff0c;许多大卖家开始选择自建测评团队。 下面&#xff0c;将为你详细阐述自养号的优势…

成绩等级分数段查询(python条件分支语句match...case...)

根据有效分数序列及等级差值&#xff0c;计算并打印等级相应分数区间。 (笔记模板由python脚本于2024年01月20日 23:57:32创建&#xff0c;本篇笔记适合会条件分支语句的初学者的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&…

Oracle1 数据库管理

Oracle的安装 一、基础表的创建 1.1 切换到scott用户 用sys 账户 登录 解锁scott账户 alter user scott account unlock;conn scott/tiger;发现并不存在scott账户&#xff0c;自己创建一个&#xff1f; 查找资料后发现&#xff0c;scott用户的脚本需要自己执行一下 C:\ap…

Java面试题50道

文章目录 1.谈谈你对Spring的理解2.Spring的常用注解有哪些3.Spring中的bean线程安全吗4.Spring中的设计模式有哪些5.Spring事务传播行为有几种6.Spring是怎么解决循环依赖的7.SpringBoot自动配置原理8.SpringBoot配置文件类型以及加载顺序9.SpringCloud的常用组件有哪些10.说一…

【JavaEE进阶】 关于⽇志框架(SLF4J)

文章目录 &#x1f333;SLF4j&#x1f332;⻔⾯模式(外观模式)&#x1f6a9;⻔⾯模式的定义&#x1f6a9;⻔⾯模式的优点 &#x1f343;关于SLF4J框架&#x1f6a9;不引⼊⽇志⻔⾯&#x1f6a9;引⼊⽇志⻔⾯ ⭕总结 &#x1f333;SLF4j SLF4J不同于其他⽇志框架,它不是⼀个真正…

ETF是什么?为什么要做ETF?做ETF的好处是什么?

现在越来越多的人都在做ETF了。那ETF到底怎么做&#xff1f;大家对股票和基金都不陌生&#xff0c;但对ETF就不太熟悉了。今天&#xff0c;咱们就对ETF做个相对全面的介绍&#xff0c;希望对大家投资有所帮助哦&#xff01;ETF具体操作详情查看&#xff0c;注意结尾惊喜福利&am…

CGLIB动态代理(AOP原理)(面试重点)

推荐先看JDK 动态代理&#xff08;Spring AOP 的原理&#xff09;&#xff08;面试重点&#xff09; JDK 动态代理与 CGLIB 动态代理的区别 JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类. 有些场景下,我们的业务代码是直接实现的,并没有接⼝定义.为了解决这个问…

使用禅道进行项目管理_后台自定义功能介绍

文章目录 前言一、前置权限二、自定义功能概述三、自定义1. 自定义“研发需求”2. 自定义“任务”3. 自定义“Bug”4. 自定义“用例”5. 自定义“测试单”6. 自定义“待办”7. 自定义“用户”8. 自定义“必填项” 总结 前言 禅道是一款国产开源的项目管理软件&#xff0c;它基…

公网环境调试本地配置的Java支付宝沙箱环境模拟支付场景

文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问 前言 在沙箱环境调试支付SDK的时候&#xff0c;往往沙箱环境部署在本地&#xff0c;局限性大&#xff0c;在沙箱环境…