Appearance
Gateway 组件
1.1 认识网关
什么是网关?
网关是网络的关口,负责数据在不同网络间传输时的路由、转发和安全校验。它充当微服务架构中的统一入口,所有外部请求都需要先经过网关才能访问后端微服务。
网关的作用
- 安全控制:进行身份认证和权限校验
- 请求路由:根据规则将请求转发到对应的微服务
- 负载均衡:在多个服务实例间分配请求
- 限流熔断:保护后端服务不被过多请求压垮
SpringCloud 网关方案
- Netflix Zuul:早期实现,目前已淘汰
- SpringCloud Gateway:基于 WebFlux 的响应式网关,性能更优
1.2 快速入门
创建网关微服务
1. 创建项目模块
在 mall 项目下创建 hj-gateway 模块
2. 添加依赖
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hj</groupId>
<artifactId>mall</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>hj-gateway</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- 公共模块 -->
<dependency>
<groupId>com.hj</groupId>
<artifactId>hj-common</artifactId>
<version>1.0.0</version>
</dependency>
<!-- SpringCloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>3. 创建启动类
java
package com.hj.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}4. 配置路由规则
yaml
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.12.168:8848
gateway:
routes:
- id: item
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
- id: cart
uri: lb://cart-service
predicates:
- Path=/carts/**
- id: user
uri: lb://user-service
predicates:
- Path=/users/**,/addresses/**
- id: trade
uri: lb://trade-service
predicates:
- Path=/orders/**
- id: pay
uri: lb://pay-service
predicates:
- Path=/pay-orders/**5. 测试验证
启动网关和相关微服务,通过网关地址访问:
http://localhost:8080/items/page?pageNo=1&pageSize=11.3 路由过滤
路由配置结构
路由配置包含四个核心属性:
- id:路由规则唯一标识
- uri:目标服务地址,
lb://表示负载均衡 - predicates:路由断言,定义匹配条件
- filters:路由过滤器,处理请求和响应
常用路由断言
| 断言类型 | 说明 | 示例 |
|---|---|---|
| Path | 路径匹配 | - Path=/api/** |
| After | 在指定时间后生效 | - After=2023-01-20T17:42:47.789-07:00[America/Denver] |
| Before | 在指定时间前生效 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
| Between | 在时间区间内生效 | - Between=startTime,endTime |
| Cookie | 必须包含指定 Cookie | - Cookie=sessionId, .* |
| Header | 必须包含指定 Header | - Header=X-Request-Id, \d+ |
| Method | 请求方法匹配 | - Method=GET,POST |
| Query | 必须包含指定参数 | - Query=token |
| RemoteAddr | IP 地址匹配 | - RemoteAddr=192.168.1.1/24 |
内置过滤器示例
yaml
spring:
cloud:
gateway:
routes:
- id: item
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
filters:
- AddRequestHeader=Authorization,Bearer token123
- AddRequestParameter=source,gateway2.1 鉴权思路分析
微服务架构下的认证挑战
- 每个微服务都需要重复实现登录校验
- JWT 秘钥需要在多个服务中保存,存在安全风险
- 代码重复,维护困难
网关统一认证方案
将登录校验前置到网关层:
- 网关统一处理身份认证
- 只有网关和用户服务持有 JWT 秘钥
- 减少代码重复,提高安全性
2.2 网关过滤器
Gateway 工作原理
- 请求进入网关的
HandlerMapping - 匹配到对应的路由规则 (
Route) - 交给
WebHandler处理,加载过滤器链 - 按顺序执行过滤器的
pre逻辑 - 路由到微服务
- 倒序执行过滤器的
post逻辑 - 返回响应结果
过滤器类型
- GatewayFilter:路由过滤器,作用于特定路由
- GlobalFilter:全局过滤器,作用于所有路由
2.3 自定义过滤器
自定义 GlobalFilter
java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final JwtTool jwtTool;
private final AuthProperties authProperties;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求信息
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
// 2. 检查是否在排除路径中
if (isExcludePath(path)) {
return chain.filter(exchange);
}
// 3. 获取并验证Token
String token = getTokenFromHeader(request);
try {
Long userId = jwtTool.parseToken(token);
// 4. 将用户信息添加到请求头
ServerHttpRequest newRequest = request.mutate()
.header("user-info", userId.toString())
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (Exception e) {
// 5. Token验证失败,返回401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
}
private boolean isExcludePath(String path) {
return authProperties.getExcludePaths().stream()
.anyMatch(pattern -> antPathMatcher.match(pattern, path));
}
private String getTokenFromHeader(ServerHttpRequest request) {
List<String> headers = request.getHeaders().get("authorization");
return headers != null && !headers.isEmpty() ? headers.get(0) : null;
}
@Override
public int getOrder() {
return 0;
}
}配置类
java
@Data
@ConfigurationProperties(prefix = "hj.auth")
public class AuthProperties {
private List<String> excludePaths = new ArrayList<>();
}yaml
hj:
auth:
excludePaths:
- /search/**
- /users/login
- /items/**2.4 JWT 工具集成
添加 JWT 相关配置
yaml
hj:
jwt:
location: classpath:mall.jks
alias: mall
password: mall123
tokenTTL: 30mJWT 工具类
java
@Component
@EnableConfigurationProperties(JwtProperties.class)
public class JwtTool {
private final JwtProperties properties;
private final JwtParser jwtParser;
public JwtTool(JwtProperties properties) {
this.properties = properties;
// 初始化JWT解析器
this.jwtParser = Jwts.parserBuilder()
.setSigningKey(getPublicKey())
.build();
}
public Long parseToken(String token) {
if (token == null || token.trim().isEmpty()) {
throw new JwtException("Token不能为空");
}
try {
Claims claims = jwtParser.parseClaimsJws(token).getBody();
return claims.get("userid", Long.class);
} catch (Exception e) {
throw new JwtException("Token解析失败", e);
}
}
private PublicKey getPublicKey() {
// 从秘钥库加载公钥
// 具体实现略...
}
}2.5 用户信息传递
微服务拦截器
java
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 1. 从请求头获取用户信息
String userInfo = request.getHeader("user-info");
// 2. 保存到ThreadLocal
if (StringUtils.hasText(userInfo)) {
UserContext.setUser(Long.valueOf(userInfo));
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
// 清理ThreadLocal
UserContext.removeUser();
}
}自动配置
java
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}在 META-INF/spring.factories 中注册:
properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hj.common.config.MvcConfig2.6 OpenFeign 用户传递
Feign 请求拦截器
java
public class DefaultFeignConfig {
@Bean
public RequestInterceptor userInfoRequestInterceptor() {
return template -> {
Long userId = UserContext.getUser();
if (userId != null) {
template.header("user-info", userId.toString());
}
};
}
}购物车业务逻辑
java
@Service
@RequiredArgsConstructor
public class CartServiceImpl implements ICartService {
@Override
public List<CartVO> queryMyCarts() {
// 从ThreadLocal获取当前用户ID
Long userId = UserContext.getUser();
if (userId == null) {
throw new RuntimeException("用户未登录");
}
// 查询该用户的购物车
return cartMapper.listCarts(userId);
}
}