微服务网关限流与鉴权
微服务网关设计与实现
- 微服务网关概述
在分布式系统中,不同微服务通常运行在不同的网络地址上。如果客户端直接调用这些服务的接口,会面临以下问题:
- 客户端需要多次请求多个微服务,增加了复杂性。
- 可能存在跨域请求问题。
- 认证逻辑分散在每个微服务中,难以统一管理。
- 随着项目迭代,重构微服务架构变得困难。
这些问题可以通过引入网关来解决。网关作为客户端和服务器之间的中间层,可以集中处理安全、性能监控和认证授权等功能。
1.1 网关技术选型
常见的网关技术包括:
- Nginx: 高性能HTTP和反向代理服务器。
- Zuul: Netflix开发的基于JVM的路由和服务负载均衡器。
- Spring Cloud Gateway: Spring生态系统中的新一代网关解决方案,支持路径重写和断路器功能。
- Spring Cloud Gateway 实现
2.1 环境搭建
以下是基于Spring Cloud Gateway构建微服务网关的基本步骤:
- 创建Maven工程,并添加依赖项:
<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>
- 编写启动类:
@SpringBootApplication
@EnableEurekaClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
- 配置文件:
spring:
application:
name: sysgateway
cloud:
gateway:
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
2.2 跨域支持
通过修改application.yml文件,可以轻松实现跨域支持:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
2.3 自定义过滤器
可以通过自定义过滤器实现IP黑白名单、URL拦截等功能:
@Component
public class IpFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
InetSocketAddress address = request.getRemoteAddress();
System.out.println("Request IP: " + address.getHostName());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
@Component
public class UrlFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
System.out.println("Accessing URL: " + url);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 2;
}
}
- 网关限流
3.1 限流算法简介
常用的限流算法有令牌桶算法。其核心思想是通过控制令牌生成速率限制请求频率。
3.2 基于Redis的限流实现
以下是基于Redis的限流配置:
- 添加Redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
- 定义KeyResolver:
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
- 更新application.yml:
spring:
cloud:
gateway:
routes:
- id: goods
uri: lb://goods
predicates:
- Path=/goods/**
filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
- BCrypt 密码加密
BCrypt是一种安全的密码加密算法,支持加盐机制防止彩虹表攻击。
// 加密示例
String hashedPassword = BCrypt.hashpw("123456", BCrypt.gensalt());
// 校验示例
boolean matches = BCrypt.checkpw("123456", hashedPassword);
System.out.println("Password matches: " + matches);
- JWT 实现微服务鉴权
JSON Web Token (JWT) 是一种轻量级的安全信息传递标准,广泛用于微服务鉴权。
5.1 JWT 构造与解析
一个JWT由三部分组成:头部、载荷和签名。
// 创建Token
String token = Jwts.builder()
.setId("123")
.setSubject("user")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "secret")
.compact();
// 解析Token
Claims claims = Jwts.parser()
.setSigningKey("secret")
.parseClaimsJws(token)
.getBody();
5.2 网关鉴权实现
通过自定义过滤器验证JWT令牌:
@Component
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || !isValidToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
private boolean isValidToken(String token) {
try {
Jwts.parser().setSigningKey("secret").parseClaimsJws(token).getBody();
return true;
} catch (Exception e) {
return false;
}
}
}