title: 微服务网关限流&鉴权
date: 2022-01-06 14:40:45.047
updated: 2022-01-06 14:40:45.047
url: http://www.yby6.com/archives/wei-fu-wu-wang-guan-xian-liu--jian-quan
categories:
tags:
- 微服务
- 鉴权
不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
以上这些问题可以借助网关解决。
网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 网关来做,这样既提高业务灵活性又不缺安全性,典型的架构图如图所示:
优点如下:
总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控的相关功能。
实现微服务网关的技术有很多,
我们使用gateway这个网关技术,无缝衔接到基于spring cloud的微服务开发中来。
gateway官网:
http://spring.io/projects/spring-cloud-gateway
由于我们开发的系统 有包括前台系统和后台系统,后台的系统给管理员使用。那么也需要调用各种微服务,所以我们针对管理后台搭建一个网关微服务。分析如下:
搭建步骤:
(1)在changgou_gateway工程中,创建changgou_gateway_system工程,pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)创建包com.changgou.system, 创建引导类:GatewayApplication
@SpringBootApplication
@EnableEurekaClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
(3)在resources下创建application.yml
spring:
application:
name: sysgateway
cloud:
gateway:
routes:
- id: goods
uri: lb://goods
predicates:
- Path=/goods/**
filters:
- StripPrefix= 1
- id: system
uri: lb://system
predicates:
- Path=/system/**
filters:
- StripPrefix= 1
server:
port: 9101
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
参考官方手册:
http://cloud.spring.io/spring-cloud-gateway/spring-cloud-gateway.html#_stripprefix_gatewayfilter_factory
修改application.yml ,在spring.cloud.gateway节点添加配置,
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
最终配置文件如下:
spring:
application:
name: sysgateway
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
- id: goods
uri: lb://goods
predicates:
- Path=/goods/**
filters:
- StripPrefix= 1
server:
port: 9101
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
我们可以通过网关过滤器,实现一些逻辑的处理,比如ip黑白名单拦截、特定地址的拦截等。下面的代码中做了两个过滤器,并且设定的先后顺序,只演示过滤器与运行效果。
(1)changgou_gateway_system创建IpFilter
@Component
public class IpFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("经过第1个过滤器IpFilter");
ServerHttpRequest request = exchange.getRequest();
InetSocketAddress remoteAddress = request.getRemoteAddress();
System.out.println("ip:"+remoteAddress.getHostName());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
(2)changgou_gateway_system创建UrlFilter
@Component
public class UrlFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("经过第2个过滤器UrlFilter");
ServerHttpRequest request = exchange.getRequest();
String url = request.getURI().getPath();
System.out.println("url:"+url);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 2;
}
}
测试,观察控制台输出。
我们之前说过,网关可以做很多的事情,比如,限流,当我们的系统被频繁的请求的时候,就有可能将系统压垮,所以为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。
令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)根据限流大小,设置按照一定的速率往桶里添加令牌;
3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流
如下图:
这个算法的实现,有很多技术,Guava(读音: 瓜哇)是其中之一,redis客户端也有其实现。
需求:每个ip地址1秒内只能发送1次请求,多出来的请求返回429错误。
代码实现:
(1)spring cloud gateway 默认使用redis的RateLimter限流算法来实现。所以我们要使用首先需要引入redis的依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
(2)定义KeyResolver
在GatewayApplicatioin引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。
//定义一个KeyResolver
@Bean
public KeyResolver ipKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
};
}
(3)修改application.yml中配置项,指定限制流量的配置以及REDIS的配置,修改后最终配置如下:
spring:
application:
name: sysgateway
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
- id: goods
uri: lb://goods
predicates:
- Path=/goods/**
filters:
- StripPrefix= 1
- name: RequestRateLimiter #请求数限流 名字不能随便写
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 1 #令牌桶总容量
- id: system
uri: lb://system
predicates:
- Path=/system/**
filters:
- StripPrefix= 1
# 配置Redis 127.0.0.1可以省略配置
redis:
host: 192.168.200.128
port: 6379
server:
port: 9101
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
解释:
通过在replenishRate
和中设置相同的值来实现稳定的速率burstCapacity
。设置burstCapacity
高于时,可以允许临时突发replenishRate
。在这种情况下,需要在突发之间允许速率限制器一段时间(根据replenishRate
),因为2次连续突发将导致请求被丢弃(HTTP 429 - Too Many Requests
)
key-resolver: "#{@userKeyResolver}" 用于通过SPEL表达式来指定使用哪一个KeyResolver.
如上配置:
表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。
最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。
(4)测试
启动redis
启动注册中心
启动商品微服务
启动gateway网关
打开浏览器 http://localhost:9101/goods/brand
快速刷新,当1秒内发送多次请求,就会返回429错误。
在用户模块,对于用户密码的保护,通常都会进行加密。我们通常对密码进行加密,然后存放在数据库中,在用户进行登录的时候,将其输入的密码进行加密然后与数据库中存放的密文进行比较,以验证用户密码是否正确。 目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全。因为其内部引入的加盐机制
BCrypt 官网http://www.mindrot.org/projects/jBCrypt/
(1)新建测试类,main方法中编写代码,实现对密码的加密
public class TestBcrypt {
public static void main(String[] args) {
/**
* 得到盐
* 盐是一个随机生成的含有29个字符的字符串,并且会与密码一起合并进行最终的密文生成
* 并且每一次生成的盐的值都是不同的
*/
for(int i=0;i<10;i++){
String gensalt = BCrypt.gensalt();
System.out.println("salt:"+gensalt);
String saltPassword = BCrypt.hashpw("123456", gensalt);
System.out.println("本次生成的密码:"+saltPassword);
}
}
}
(2)main方法中编写代码,实现对密码的校验。BCrypt不支持反运算,只支持密码校验。
//校验密码
boolean checkpw = BCrypt.checkpw("123456", saltPassword);
System.out.println("密码校验结果:"+checkpw);
新增管理员,使用BCrypt进行密码加密
id | int | 主键id |
---|---|---|
login_name | varchar | 登录名 |
password | varchar | 密码 |
status | char | 状态 |
(1)修改changgou_service_system项目的AdminServiceImpl
/**
* 增加
* @param admin
*/
@Override
public void add(Admin admin){
String password = BCrypt.hashpw(admin.getPassword(), BCrypt.gensalt());
admin.setPassword(password);
adminMapper.insert(admin);
}
系统管理用户需要管理后台,需要先输入用户名和密码进行登录,才能进入管理后台。
思路:
用户发送请求,输入用户名和密码
后台管理微服务controller接收参数,验证用户名和密码是否正确,如果正确则返回用户登录成功结果
(1)AdminService新增方法定义
/**
* 登录验证密码
* @param admin
* @return
*/
boolean login(Admin admin);
(2)AdminServiceImpl实现此方法
@Override
public boolean login(Admin admin) {
//根据登录名查询管理员
Admin admin1=new Admin();
admin1.setLoginName(admin.getLoginName());
admin1.setStatus("1");
Admin admin2 = adminMapper.selectOne(admin1);//数据库查询出的对象
if(admin2==null){
return false;
}else{
//验证密码, Bcrypt为spring的包, 第一个参数为明文密码, 第二个参数为密文密码
return BCrypt.checkpw(admin.getPassword(),admin2.getPassword());
}
}
(3)AdminController新增方法
/**
* 登录
* @param admin
* @return
*/
@PostMapping("/login")
public Result login(@RequestBody Admin admin){
boolean login = adminService.login(admin);
if(login){
return new Result();
}else{
return new Result(false,StatusCode.LOGINERROR,"用户名或密码错误");
}
}
由于在学习JWT的时候会涉及使用很多加密算法, 所以在这里做下扫盲, 简单了解就可以
加密算法种类有:
解释: 加密后, 密文可以反向解密得到密码原文.
【文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥】
解释: 在对称加密算法中,数据发信方将明文和加密密钥一起经过特殊的加密算法处理后,使其变成复杂的加密密文发送出去,收信方收到密文后,若想解读出原文,则需要使用加密时用的密钥以及相同加密算法的逆算法对密文进行解密,才能使其回复成可读明文。在对称加密算法中,使用的密钥只有一个,收发双方都使用这个密钥,这就需要解密方事先知道加密密钥。
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
缺点: 没有非对称加密安全.
用途: 一般用于保存用户手机号、身份证等敏感但能解密的信息。
常见的对称加密算法有: AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256
【两个密钥:公开密钥(publickey)和私有密钥,公有密钥加密,私有密钥解密】
**解释: ** 同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端.
加密与解密:
私钥加密,持有私钥或公钥才可以解密
公钥加密,持有私钥才可解密
签名:
**优点: ** 非对称加密与对称加密相比,其安全性更好;
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
用途: 一般用于签名和认证。私钥服务器保存, 用来加密, 公钥客户拿着用于对于令牌或者签名的解密或者校验使用.
常见的非对称加密算法有: RSA、DSA(数字签名用)、ECC(移动设备用)、RS256 (采用SHA-256 的 RSA 签名)
解释: 一旦加密就不能反向解密得到密码原文.
种类: Hash加密算法, 散列算法, 摘要算法等
用途:一般用于效验下载文件正确性,一般在网站上下载文件都能见到;存储用户敏感信息,如密码、 卡号等不可解密的信息。
常见的不可逆加密算法有: MD5、SHA、HMAC
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。采用Base64编码解码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。注意:Base64只是一种编码方式,不算加密方法。
在线编码工具:
http://www.jsons.cn/img2base64/
我们之前已经搭建过了网关,使用网关在系统中比较适合进行权限校验。
那么我们可以采用JWT的方式来实现鉴权校验。
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{"typ":"JWT","alg":"HS256"}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码http://base64.xpcha.com/,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(playload)
载荷就是存放有效信息的地方。
定义一个payload:
{"sub":"1234567890","name":"John Doe","admin":true}
然后将其进行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
官方文档:
http://github.com/jwtk/jjwt
(1)新建项目jwtTest中的pom.xml中添加依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
(2) 创建测试类,代码如下
JwtBuilder builder= Jwts.builder()
.setId("888") //设置唯一编号
.setSubject("小白")//设置主题 可以是JSON数据
.setIssuedAt(new Date())//设置签发日期
.signWith(SignatureAlgorithm.HS256,"itcast");//设置签名 使用HS256算法,并设置SecretKey(字符串)
//构建 并返回一个字符串
System.out.println( builder.compact() );
运行打印结果:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXXreFU_u3Y
再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。
我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDQxODF9.ThecMfgYjtoys3JX7dpx3hu6pUm0piZ0tXXreFU_u3Y";
Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody();
System.out.println(claims);
运行打印效果:
{jti=888, sub=小白, iat=1557904181}
试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token.
有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。
(1)创建token 并设置过期时间
//当前时间
long currentTimeMillis = System.currentTimeMillis();
Date date = new Date(currentTimeMillis);
JwtBuilder builder= Jwts.builder()
.setId("888") //设置唯一编号
.setSubject("小白")//设置主题 可以是JSON数据
.setIssuedAt(new Date())//设置签发日期
.setExpiration(date)
.signWith(SignatureAlgorithm.HS256,"itcast");//设置签名 使用HS256算法,并设置SecretKey(字符串)
//构建 并返回一个字符串
System.out.println( builder.compact() );
解释:
.setExpiration(date)//用于设置过期时间 ,参数为Date类型数据
运行,打印效果如下:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI
(2)解析TOKEN
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDUzMDgsImV4cCI6MTU1NzkwNTMwOH0.4q5AaTyBRf8SB9B3Tl-I53PrILGyicJC3fgR3gWbvUI";
Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody();
System.out.println(claims);
打印效果:
当前时间超过过期时间,则会报错。
我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims。
创建测试类,并设置测试方法:
创建token:
@Test
public void createJWT(){
//当前时间
long currentTimeMillis = System.currentTimeMillis();
currentTimeMillis+=1000000L;
Date date = new Date(currentTimeMillis);
JwtBuilder builder= Jwts.builder()
.setId("888") //设置唯一编号
.setSubject("小白")//设置主题 可以是JSON数据
.setIssuedAt(new Date())//设置签发日期
.setExpiration(date)//设置过期时间
.claim("roles","admin")//设置角色
.signWith(SignatureAlgorithm.HS256,"itcast");//设置签名 使用HS256算法,并设置SecretKey(字符串)
//构建 并返回一个字符串
System.out.println( builder.compact() );
}
运行打印效果:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDU4MDIsImV4cCI6MTU1NzkwNjgwMiwicm9sZXMiOiJhZG1pbiJ9.AS5Y2fNCwUzQQxXh_QQWMpaB75YqfuK-2P7VZiCXEJI
解析TOKEN:
//解析
@Test
public void parseJWT(){
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDU4MDIsImV4cCI6MTU1NzkwNjgwMiwicm9sZXMiOiJhZG1pbiJ9.AS5Y2fNCwUzQQxXh_QQWMpaB75YqfuK-2P7VZiCXEJI";
Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJwt).getBody();
System.out.println(claims);
}
运行效果:
1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
(1)在changgou_service_system添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
(2)在changgou_service_system中创建类: JwtUtil
package com.changgou.system.util;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "itcast";
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("admin") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);// 设置过期时间
return builder.compact();
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
(3)修改AdminController的login方法, 用户登录成功 则 签发TOKEN
/**
* 登录
* @param admin
* @return
*/
@PostMapping("/login")
public Result login(@RequestBody Admin admin){
boolean login = adminService.login(admin);
if(login){ //如果验证成功
Map<String,String> info = new HashMap<>();
info.put("username",admin.getLoginName());
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), admin.getLoginName(), null);
info.put("token",token);
return new Result(true, StatusCode.OK,"登录成功",info);
}else{
return new Result(false,StatusCode.LOGINERROR,"用户名或密码错误");
}
}
使用postman 测试
(1)在changgou_gateway_system网关系统添加依赖
<!--鉴权-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
(2)创建JWTUtil类
package com.changgou.gateway.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* jwt校验工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "itcast";
/**
* 生成加密后的秘钥 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
(3)创建过滤器,用于token验证
/**
* 鉴权过滤器 验证token
*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
private static final String AUTHORIZE_TOKEN = "token";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1. 获取请求
ServerHttpRequest request = exchange.getRequest();
//2. 则获取响应
ServerHttpResponse response = exchange.getResponse();
//3. 如果是登录请求则放行
if (request.getURI().getPath().contains("/admin/login")) {
return chain.filter(exchange);
}
//4. 获取请求头
HttpHeaders headers = request.getHeaders();
//5. 请求头中获取令牌
String token = headers.getFirst(AUTHORIZE_TOKEN);
//6. 判断请求头中是否有令牌
if (StringUtils.isEmpty(token)) {
//7. 响应中放入返回的状态吗, 没有权限访问
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//8. 返回
return response.setComplete();
}
//9. 如果请求头中有令牌则解析令牌
try {
JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//11. 返回
return response.setComplete();
}
//12. 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
(4)测试:
注意: 数据库中管理员账户为 : admin , 密码为 : 123456
如果不携带token直接访问,则返回401错误
如果携带正确的token,则返回查询结果
你的压力来源于无法自律,只是假装努力,现状跟不上内心欲望,所以你焦虑又恐慌。——杨不易今天是日更的33/365天大家好,我是阿常。上一篇给大家介绍了软件测试的目的,今天阿常和大家说说软件测试的方法。●从是否执行程序的角度来划分●从是否关心软件内部结构和具体实现的角度来划分●根据不同阶段来划分●根据是否需要手工执行来划分一、从是否执行程序的角度来划分1、静态测试静态测试包括代码检查法、静态结构分析法、代码质量度量法。2、动态测试动态测试由三部分构成:构造测试实例、执行程序、分析程序的输出结果。二、从是否关心软件内部结构和具体实现的角度来划分1、白盒测试白盒测试主要方法:代码检查法、静态结构分析法、代码质量度量法、逻辑覆盖法、基本路径测试法、域测试、符号测试、路径覆盖、程序变异等。2、黑盒测试黑盒测试主要方法:等价类划分法、边界值分析法、错误推测法、因果图法、判定表驱动法、正交实验设计法、功能图法、场景法等。3、灰盒测试灰盒测试是通过类似白盒测试的方法进行的,是通过编写代码、调用函数或者封装好的接口进行,但无需关心程序内部的实现细节,依然可把它当成一个黑盒。三、根据不同阶段来划分1、单元测试单元测试是对软件基本组成单元进行的测试,如函数或是一个类的方法,属于白盒测试的范畴。
SAPRETAIL如何通过分配表查到根据它创建的采购订单?在SAPRETAIL系统中,我们可以创建好分配表,然后通过分配表可以批量创建采购订单。这个功能在零售行业里会使用到,当有新的门店要营业或者其它场景比如大促销的时候,业务部门需要提前铺货。笔者在某个流程行业SAP项目的蓝图文档里就看到有一个叫做铺货的流程,在该流程里他们有启用分配表的功能去批量触发采购订单,大量采购商品过来铺货。SAP系统是一个高度集成的系统,业务流程里上下游单据之间也讲究关联和追溯,方便业务人员迅速查找到上下游业务活动所创建的单据。通过分配表触发的后续的采购订单,补货订单等等单据,也可以在分配表的相关界面里找到。比如如下的分配表10,已经通过WA08事务代码触发了采购订单的。如果想知道它的后继采购订单数据,如下方式可以查询到。1,执行事务代码WA03,进入分配表的显示界面:选中行项目,点击按钮,进入如下界面,2,选中某个item,点击按钮,进入如下界面,在Administrationdata选项卡里,就能看到vendororder,如上图。3,而在这个采购订单的itemdetail里的Retail选项卡,则能很方
一、先理解内核空间与用户空间Linux按照特权等级,把进程的运行空间分为内核空间和用户空间,分别对应着下图中,CPU特权等级分为4个,Linux使用Ring0和Ring3。内核空间(Ring0)具有最高权限,可以直接访问所有资源,;用户空间(Ring3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。上面的Ring图可以简化成:内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。Java学习笔记共享地址:13.BIO,NIO,AIO,Nett学习笔记系统调用是操作系统的最小功能单位,通过提供一些基本功能的接口供应用程序调用来调度内核空间管理的资源可以把系统调用比作一个汉字的一个“笔画”,而一个“汉字”就代表一个上层应用。因此,有时候如果要实现一个完整的汉字(给某个变量分配内存空间),就必须调用很多的系统调用(笔画)。如果
什么是图片懒加载图片的懒加载就是在页面打开的时候,不要一次性全部显示页面所有的图片,而是只显示当前视口内的图片,一般在移动端使用(PC端主要是前端分页或者后端分页)。为什么需要懒加载对于一个页面加载速度影响最大的因素之一就是图片资源,如果一个页面图片太多(比如某宝,某东等),整个页面的图片大小可以到达几百兆,即使在百兆宽带,全部下载的话,也需要上十秒的时间,这对于用户耐心的考验是巨大的,更别说网络差的地方了。因此,懒加载是必须要做的,对于页面未在可视区域内显示的图片先不做加载处理,只加载第一映入眼帘的图片,由于可视区域显示的图片少,加载速度就会大大提升,用户体验也会更好。而且,用户可能只翻看一两页就退出了,剩下未查看的图片也就不需要加载了。这也相当于节省了带宽资源。懒加载实现原理由于浏览器会自动对页面中的img标签的src属性发送请求并下载图片。因此,通过html5自定义属性data-xxx先暂存src的值,然后在需要显示的时候,再将data-xxx的值重新赋值到img的src属性即可。实现代码这里模拟两种情况:情况一1、前端已经获取到所有的图片了,现在需要将这些图片以懒加载的形式展示
作者:翟志军 https://showme.codes/2017-02-20/understand-https/本文尝试一步步还原HTTPS的设计过程,以理解为什么HTTPS最终会是这副模样。但是这并不代表HTTPS的真实设计过程。在阅读本文时,你可以尝试放下已有的对HTTPS的理解,这样更利于“还原”过程。我们先不了聊HTTP,HTTPS,我们先从一个聊天软件说起,我们要实现A能发一个hello消息给B:如果我们要实现这个聊天软件,本文只考虑安全性问题,要实现A发给B的hello消息包,即使被中间人拦截到了,也无法得知消息的内容如何做到真正的安全?这个问题,很多人马上就想到了各种加密算法,什么对称加密、非对称加密、DES、RSA、XX、噼里啪啦~而我想说,加密算法只是解决方案,我们首先要做的是理解我们的问题域——什么是安全?我个人的理解是:A与B通信的内容,有且只有A和B有能力看到通信的真正内容好,问题域已经定义好了(现实中当然不止这一种定义)。对于解决方案,很容易就想到了对消息进行加密。题外话,但是只有这一种方法吗?我看未必,说不定在将来会出现一种物质打破当前世界的通信假设,实现真
发展:BIO->NIO->AIOBIO就是我们传统意义上的IO,它的特点是阻塞的。例如以前我们进行网络编程时,一个客户端使用一个线程来进行处理。这样会导致一个问题:服务器的线程数是有限制的,而不是每个客户端时时都有数据进行传输的。所以大量空的线程占了位置,但是又不工作,导致服务器的性能受限。这会涉及到4次上下文切换和4次拷贝。第一次切换:从用户态切换到内核态,将数据读取到内核第二次切换:从内核态切换到用户态,读取返回第三次切换:从用户态切换到内核态,将数据拷贝到内核第四次切换:从内核态切换到用户态,写完返回四次拷贝:1.通过DMA将数据从硬件拷贝到内核2.通过CPU将数据从内核拷贝到用户缓冲3.通过CPU将数据从用户缓冲拷贝到内核4.从内核写入到硬件NIO:一个请求对应一个线程,解决BIO高并发的问题。会涉及到3次拷贝,2次切换:第一次拷贝:从硬件到内核空间第二次拷贝:从内核空间到socketbuffer第三次拷贝:从socketbuffer到protocolengine第一次切换:从用户态切换到内核态,数据通过DMA将数据拷贝到内核第二次切换:数据写完到硬件后返回,从内核
上一节的统一配置中心《SpringCloud系列第08节之配置中心Config》演示了属性热加载其中提到,每次热加载属性时,都要逐次调用每个应用的 /refresh 接口(或者维护Git仓库的Webhooks)来触发属性更新随着系统的扩充,应用的增加,若所有的触发动作都要手工去做(或者维护Git仓库的Webhooks),这是不人道的所以我们希望配置中心的属性发生变化时,能有一种途径去通知所有的相关应用去自动刷新配置而通过SpringCloudBus就能够实现以消息总线的方式,通知集群上的应用,去动态更新配置信息本文是以RabbitMQ来作为消息代理的中间件(实现将消息路由到一个或多个目的地),所以要先安装RabbitMQRabbitMQ的安装RabbitMQ是AMQP(AdvancedMessageQueuingProtocol)协议的一个开源实现的产品它是由以高性能、健壮、可伸缩性出名的ErlangOTP平台实现的工业级的消息队列服务器所以在安装RabbitMQ之前,要先安装Erlang,下面是它们的下载地址http://erlang.org/download/otp_win64_1
高级的数据分析会涉及回归分析、方差分析和T检验等方法,不要看这些内容貌似跟日常工作毫无关系,其实往高处走,MBA的课程也是包含这些内容的,所以早学晚学都得学,干脆就提前了解吧,请查看以下内容。在使用之前,首先得安装Excel的数据分析功能,默认情况下,Excel是没有安装这个扩展功能的,安装如下所示:1)鼠标悬浮在Office按钮上,然后点击【Excel选项】:2)找到【加载项】,在管理板块选择【Excel加载项】,然后点击【转到】:3)选择【分析工具库】,点击【确定】:4)安装完后,就可以【数据】板块看到【数据分析】功能,如下所示:安装完后,首先来了解一下回归分析的内容。回归分析在详细进行回归分析之前,首先要理解什么叫回归? 实际上,回归这种现象最早由英国生物统计学家高尔顿在研究父母亲和子女的遗传特性时所发现的一种有趣的现象:身高这种遗传特性表现出”高个子父母,其后代身高也高于平均身高;但不见得比其父母更高,到一定程度后会往平均身高方向发生’回归’”。 这种效应被称为”趋中回归”。现在的回归分析则多半指源于高尔顿工作的那样一整套建立变量间的数量关系模型的方法和程序。这里的自变量是父母
0、Redis目录结构 1)Redis介绍及部署在CentOS7上(一) 2)Redis指令与数据结构(二) 3)Redis客户端连接以及持久化数据(三) 4)Redis高可用之主从复制实践(四) 5)Redis高可用之哨兵模式Sentinel配置与启动(五) 6)Redis高可用之集群配置(六) 一、介绍 上篇文章我们讲解了Redis的结构与指令,其实很简单,我也没有过多的讲解,这次我们讲解一下Redis连接客户端以及持久化方案。 1、上文中我们针对redis的数据操作都是在服务器中使用命令执行的,当然这个也是非常安全的处理方式,那么在开发的阶段为了方便我们可是使用可视化界面连接redis, 比如RedisDesktopManager这个软件等,方便我们快速的操作数据,下面的介绍也是依据这个软件进行的。 2
首先引入dll文件ICSharpCode.SharpZipLib.dll管理NuGet包里面下载 压缩文件 ///<summary> ///压缩文件 ///</summary> ///<paramname="fileName">要压缩的所有文件(完全路径)</param> ///<paramname="fileName">文件名称</param> ///<paramname="name">压缩后文件路径</param> ///<paramname="Level">压缩级别</param> publicvoidZipFileMain(string[]filenames,string[]fileName,stringname,intLevel) { ZipOutputStreams=newZipOutputStream(File.Create(name)); Crc32crc=newCrc32(); //压缩级别 s.SetLevel(Level);//0-storeo
s 问题1:centos8.2安装 nginx-1.23.2时,checkingforzliblibrary...notfound [root@centos82-scts08tnginx-1.23.2]#./configure--with-pcre=/opt/pcre2-10.37 、、、略 checkingforzliblibrary...notfound ./configure:error:theHTTPgzipmodulerequiresthezliblibrary.Youcaneitherdisablethemodulebyusing--without-http_gzip_moduleoption,orinstallthezliblibraryintothesystem,orbuildthezliblibrarystaticallyfromthesourcewithnginxbyusing--with-zlib=<path>option. 解决1:下载http://www.zlib.net/zlib-1.2.13.tar.gz,安装如下参考 [
华华听月月唱歌 点击做题网站链接 题目描述 月月唱歌超级好听的说!华华听说月月在某个网站发布了自己唱的歌曲,于是把完整的歌曲下载到了U盘里。然而华华不小心把U盘摔了一下,里面的文件摔碎了。月月的歌曲可以看成由1到N的正整数依次排列构成的序列,它现在变成了若干个区间,这些区间可能互相重叠。华华想把它修复为完整的歌曲,也就是找到若干个片段,使他们的并集包含1到N(注意,本题中我们只关注整数,见样例1)。但是华华很懒,所以他想选择最少的区间。请你算出华华最少选择多少个区间。因为华华的U盘受损严重,所以有可能做不到,如果做不到请输出-1。 输入描述: 第一行两个正整数N、M,表示歌曲的原长和片段的个数。 接下来M行,每行两个正整数L、R表示第i的片段对应的区间是[L,R]。 输出描述: 如果可以做到,输出最少需要的片段的数量,否则输出-1。 示例1 输入 42 12 34 输出 2 示例2 输入 42 11 34 输出 -1 示例3 输入 105 11 25 36 49 810 输出 4 备注: 1≤L≤R≤109,1≤N≤109,1≤M≤1051≤L≤R≤10^9,1≤N≤1
由于国内外环境因素,npminstall安装依赖的时候经常会出现各种问题,特别是“Error:EPERM:operationnotpermitted,unlink…”这个错误。 这个错误因为报错信息的误导性,导致很多网上提出的解决办法都是什么设置权限,以管理员身份进入等乱七八糟的。其实这个错误出现的原因就是网络不稳定,导致npm包下载不完整或者出错导致的。什么,你家里100M光纤网速飞起?但服务器是国外的,该慢的时候还是得慢。而一次出错之后,一般人都会再次npminstall,而npminstall命令并不会主动清除上次安装的包,而你上次安装的包又不完整,包与包之间又有依赖关系,结果自然就会出错。 所以,要想解决这个问题,就应该清除上次安装的包,想要彻底清除则一般需要以下2步: 删除nodemodules中的全部文件。 清除Npm缓存。 【删除nodemodules文件】的方式有两种:1、直接右键删除,缺点是依赖过多时,删除速度非常慢。2、通过安装rimraf来删除【墙裂推荐此种方法,光速】。 安装(推荐全局安装): npminstall-grimraf 使用:先进入n
台湾的Mr.6在Blog上讨论,学历是否重要? 他说: 学历不可谓不重要,但是它在人生不同的时段就有不同的意义。学历可以让你轻松入社会、轻松抢得比别人优几千倍的好位置,但以“一生只要大成功一次"的角度来看,高学历可能误导了年轻人,让他卡在某个地方,不上不下,一辈子背着一个没什么实质帮助的形象龟壳。 他的这段话,对我启发很大。 我在大学里已经待了很久了,越待越觉得学历不重要。我见过的博士数以千计,可是其中真有学问的博士寥寥无几。即使名牌大学的教授、博导和院士,相当一部分人,学术水平之低令人震惊。国内的高等院校现在培养博士,就像养鸡场一样,都是批量生产,根本谈不上质量。这种情况下,我对学历的信任早已荡然无存。有了博士学位,只能说明你读过博士课程,写过博士论文,此外什么也说明不了,你的能力依然没有得到证明。 但是,就像Mr.6所说的,学历也不能说不重要。有一个名牌大学的学位,确实有助于找到好工作,在社会中轻松占据一个好位置,为将来的发展创造条件。学历的作用恐怕仅限于此了,我都想不出第二条。 问题是“人生真正需要的是一次大的成功”,高学历只能帮助你踏入社会,并不能对你人生的成败起到决定
pytest有丰富的命令行选项,以满足不同的需要,下面对常用的命令行选项作下简单介绍。 上文已经使用过-v选项,还有很多选项,你可以使用pytest--help查看全部选项。如下图: 1、--collect-only选项 使用--collect-only选项可以展示在给定配置下哪些用例会被运行。让你方便地在测试运行之前,检查用例是否符合预期。如下例: 2、-k选项 -k选项允许你使用表达式指定希望运行的测试用例。假设希望选中test_asdict()和test_defaults(),那么可以用代表式"asdictordefaults"来筛选。结合-v或者--verbose查看是否符合预期,如下图: 3、-m选项 标记(marker)用于标记测试并分组,以便快速选中并运行。以test_replace()和test_member_access()为例,它们甚至都不在同一个文件里,如果希望同时运行它们,那么可以预先做好标记。 这里使用什么标记名由你自己决定,比如使用run_thes
把转移方程优化一下,改变决策顺序就行了。。。 #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<ctime> #include<string> #include<map> #include<vector> #include<stack> #include<queue> #include<utility> #include<iostream> #include<algorithm> template<classNum>voidread(Num&x) { charc;intflag=1; while((c=getchar())<'0'||c>'9') if(c=='-')flag*=-1; x=c-'0'; while((c=getchar())>='0'&&c<
八皇后 原题:传送门 解题思路: 典型的回溯题,题目需要我们做两件事:统计一共有多少种放法,输出前三种放法(按照字典顺序,正常顺序即可)。 核心代码: 1#include<iostream> 2#include<cstring> 3usingnamespacestd; 4 5inta[200],b[200],c[200],d[200]; 6//a数组表示的是行; 7//b数组表示的是列; 8//c表示的是左下到右上的对角线; 9//d表示的是左上到右下的对角线 10intN,ans; 11voidprint(){ 12for(inti=1;i<=N;i++){ 13cout<<a[i]<<""; 14} 15cout<<endl; 16} 17 18 19voidf(introw){ 20if(row>N){ 21if(ans<=2){ 22print(); 23} 24ans++; 25return; 26} 27el
AlictfWriteup Reverse 1. Ch1 根据题目描述,首先在Ch1.exe文件中搜索Secret.db字符串,如下所示。 之后定位文件创建和数据写入位置,如下所示。 可以看到,写入数据的地址位于esp+30h+var10,而在之前调用了data_handle函数对齐进行了处理,data_handle函数有三个参数,分别是pOutBuffer、pInBuffer、InBufferSize,如下所示。 传入数据如下所示。 处理完后,位于0x28EF3C处的数据如下所示。 可以看到,OutBuffer位于0x491B80处,如下所示。 分析到这儿,可以发现InBuffer明显不是我们所要找的flag,而其值又来源于edi,edi来源于ecx,属于寄存器传参,继续向前跟踪函数。 在这,我们发现ecx来自于edi,而edi保存的是esi+0F4h处的地址,在这中间对地址中的数据进行了处理,调用了crypto1函数进行了处理。在crypto1中,我们