为什么需要网关

什么是服务网关

传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。

微服务网关的基本功能

base function

对比一下不同的网关

这里对比一下gateway 和 zuul
gateway 网关:功能强大丰富,性能好,官方基准测试 RPS (每秒请求数)是Zuul的1.6倍,能与 SpringCloud 生态很好兼容,单从流式编程+支持异步上也足以让开发者选择它了。

(4)Zuul 2.x:性能与 gateway 差不多,基于非阻塞的,支持长连接,但 SpringCloud 没有集成 zuul2 的计划,并且 Netflix 相关组件都宣布进入维护期,前景未知。

实践

如何集成在项目中

  1. 在 Spring Cloud Gateway 中,我通过自定义 Zuul 过滤器 LoginFilter 实现了路由鉴权。LoginFilter 继承自 AbstractPreZuulFilter,属于前置过滤器,在请求路由之前执行鉴权逻辑。以下是实现的核心步骤:
  2. 路径白名单检查
    定义了一个静态的白名单集合 WHITELIST_PATHS,包含无需鉴权的路径(如 /register 和 /)。在 cRun() 方法中,获取请求路径 requestPath,并检查是否在白名单中。如果是,则直接放行,跳过 token 验证。
1
2
3
4
5
6
7
String requestPath = request.getRequestURI();
for(String str : WHITELIST_PATHS) {
if(requestPath.contains(str)) {
logger.info("Request path '{}' is whitelisted, bypassing token validation", requestPath);
return success();
}
}
  1. Token 提取与验证
  • 从请求参数中提取 token(request.getParameter(“token”))。

  • 使用 Feign 客户端 userClientFeign 调用用户服务验证 token 的有效性(userClientFeign.checkJwt(token))。

    1
    2
    3
    4
    5
    6
    7
    String token = request.getParameter("token");
    if (isTokenInvalid(token)) {
    logger.error("Token is empty or invalid");
    blockRequest(requestContext, HTTP_UNAUTHORIZED, "Token is empty or invalid");
    return null;
    }
    boolean isValidToken = validateToken(token);
  1. 处理无效 Token

如果 token 为空或无效,记录错误日志,并通过 blockRequest() 方法设置响应状态码为 401,返回错误信息。

如果 token 验证失败,调用 handleInvalidToken() 重定向用户到登录页面。

以下是使用spring cloud gateway实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 名称必须是xxxGatewayFilterFactory形式
* todo:模拟授权的验证,具体逻辑根据业务完善
*/
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

private static final String AUTHORIZE_TOKEN = "token";

//构造函数,加载Config
public AuthorizeGatewayFilterFactory() {
//固定写法
super(AuthorizeGatewayFilterFactory.Config.class);
log.info("Loaded GatewayFilterFactory [Authorize]");
}

//读取配置文件中的参数 赋值到 配置类中
@Override
public List<String> shortcutFieldOrder() {
//Config.enabled
return Arrays.asList("enabled");
}

@Override
public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
//判断是否开启授权验证
if (!config.isEnabled()) {
return chain.filter(exchange);
}

ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
//从请求头中获取token
String token = headers.getFirst(AUTHORIZE_TOKEN);
if (token == null) {
//从请求头参数中获取token
token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
}

ServerHttpResponse response = exchange.getResponse();
//如果token为空,直接返回401,未授权
if (StringUtils.isEmpty(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//处理完成,直接拦截,不再进行下去
return response.setComplete();
}
/**
* todo chain.filter(exchange) 之前的都是过滤器的前置处理
*
* chain.filter().then(
* 过滤器的后置处理...........
* )
*/
//授权正常,继续下一个过滤器链的调用
return chain.filter(exchange);
};
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Config {
// 控制是否开启认证
private boolean enabled;
}
}

同时,gatteway可以通过@order或者getOrder()防止指定执行顺序,参照zuul网关实现