在学习后端认证体系的时候,很容易遇到一个问题:
后端接口请求到底是不是一定先进入 Gateway,然后再进入 Controller?
以前我也容易把这件事想混:
前端请求
↓
Gateway
↓
Controller
好像所有后端接口前面都应该有一个 Gateway。
但真正理解单体应用和微服务架构之后,会发现这个理解并不准确。
更准确的说法是:
有 Gateway 的架构,请求才会先进入 Gateway;
没有 Gateway 的单体应用,请求会直接进入 Spring Boot 应用,再由 Spring Security 和 Spring MVC 处理。
一、先看单体应用的请求链路
单体应用通常是一个 Spring Boot 项目。
里面虽然也有很多 Controller、Service、Mapper,比如:
AuthController
UserController
OrderController
ProductController
AdminController
以及:
AuthService
UserService
OrderService
ProductService
AdminService
但是它们都在同一个 Spring Boot 进程里面。
所以单体应用的请求链路通常是:
App / Web
↓
Nginx
↓
Spring Boot 单体应用
↓
Spring Security Filter Chain
↓
DispatcherServlet
↓
Controller
↓
Service
↓
Mapper / DB / Redis
比如请求:
GET /user/info
进入 Spring Boot 之后,Spring MVC 会根据 URL 找到对应的 Controller:
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/info")
public UserInfoVO getUserInfo() {
return userService.getUserInfo();
}
}
也就是说:
GET /user/info
↓
Spring Boot
↓
Spring MVC
↓
UserController.getUserInfo()
如果请求是:
GET /order/list
那就是:
GET /order/list
↓
Spring Boot
↓
Spring MVC
↓
OrderController.list()
这里不需要 Gateway。
因为这些 Controller 本来就在同一个应用里,Spring MVC 自己就可以完成接口分发。
二、单体应用为什么通常用 Spring Security?
单体应用真正要解决的问题不是:
这个请求要转发到哪个服务?
而是:
当前请求是谁发的?
用户有没有登录?
Token 是否有效?
Redis 中的登录态是否存在?
用户有没有权限访问这个接口?
Controller 或 Service 中怎么拿到当前用户?
这些问题正是 Spring Security 擅长解决的。
单体应用中的认证链路一般是:
请求
↓
Spring Security Filter Chain
↓
JwtAuthenticationFilter
↓
解析 JWT
↓
校验 Token 是否过期
↓
校验 Redis 登录态
↓
构造 Authentication
↓
放入 SecurityContextHolder
↓
进入 Controller
比如一个常见的安全配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
JwtAuthenticationFilter jwtAuthenticationFilter
) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/login", "/auth/register").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").authenticated()
.requestMatchers("/order/**").authenticated()
.anyRequest().authenticated()
)
.addFilterBefore(
jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class
);
return http.build();
}
}
这里表达的意思是:
/auth/login 不需要登录
/auth/register 不需要登录
/admin/** 需要 ADMIN 角色
/user/** 需要登录
/order/** 需要登录
其他接口 默认需要登录
JWT Filter 负责解析 Token,并把用户信息放入 Spring Security 的上下文:
Authentication authentication =
new UsernamePasswordAuthenticationToken(
loginUser,
null,
loginUser.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
后面在 Controller 或 Service 中,就可以获取当前登录用户。
这就是 Spring Security 的价值:
认证
鉴权
用户上下文
角色权限
方法权限
安全异常处理
所以在单体应用中,Spring Security 更合适。
三、那 Gateway 到底解决什么问题?
Gateway 主要解决的是:
多个后端服务的统一入口和路由转发问题
这通常出现在微服务架构中。
微服务不是一个 Spring Boot 应用,而是多个独立运行的 Spring Boot 服务。
比如:
auth-service 8081
user-service 8082
order-service 8083
payment-service 8084
message-service 8085
每个服务都是一个独立进程。
每个服务都有自己的 Controller。
比如:
auth-service 里面有 AuthController
user-service 里面有 UserController
order-service 里面有 OrderController
payment-service 里面有 PaymentController
这时候,如果前端直接调用各个服务,就会变得很乱:
登录接口调用 auth-service
用户接口调用 user-service
订单接口调用 order-service
支付接口调用 payment-service
前端不应该关心后端拆成了多少个服务,也不应该知道每个服务的地址和端口。
所以微服务架构中通常会加一层 Gateway:
App / Web
↓
Nginx
↓
Gateway
↓
auth-service
↓
user-service
↓
order-service
↓
payment-service
Gateway 对外暴露统一地址:
https://api.xxx.com
然后根据路径做路由转发:
/auth/** → auth-service
/user/** → user-service
/order/** → order-service
/pay/** → payment-service
/message/** → message-service
比如前端请求:
GET https://api.xxx.com/order/list
Gateway 收到后判断:
/order/** 应该转发给 order-service
于是请求链路变成:
App
↓
Gateway
↓
order-service
↓
OrderController.list()
这就是 Gateway 的核心价值之一:服务路由转发。
四、Gateway 的"转发"和 Spring MVC 的"分发"不是一回事
这个区别非常关键。
1. Gateway 的转发
Gateway 的转发是跨服务、跨进程的。
比如:
Gateway 收到 /order/list
↓
转发到 order-service
这里的 order-service 是另一个独立运行的 Spring Boot 应用。
所以 Gateway 解决的是:
这个请求应该交给哪个微服务?
2. Spring MVC 的分发
Spring MVC 的分发是在当前 Spring Boot 应用内部完成的。
比如单体应用中:
Spring Boot 收到 /order/list
↓
Spring MVC 匹配 URL
↓
找到 OrderController.list()
这里没有跨进程,也没有跨服务。
所以 Spring MVC 解决的是:
当前应用内部,哪个 Controller 方法处理这个请求?
所以可以这样记:
Gateway:把请求转发给哪个服务
Spring MVC:把请求分发给哪个 Controller
再直白一点:
Gateway 是应用之间的转发
Spring MVC 是应用内部的分发
五、为什么微服务更需要 Gateway?
因为微服务拆分之后,会出现很多公共问题。
比如:
每个服务都要校验 Token 吗?
每个服务都要查 Redis 判断登录态吗?
每个服务都要做限流吗?
每个服务都要处理黑白名单吗?
每个服务都要记录入口日志吗?
如果每个服务都重复做一遍,就会出现几个问题:
代码重复
规则分散
维护困难
容易不一致
安全策略不好统一
所以微服务中通常会把这些公共入口能力放到 Gateway:
统一认证
统一鉴权
统一限流
统一跨域
统一日志
统一黑白名单
统一路由
统一灰度发布
比如认证链路可以设计成:
App 请求接口
↓
Gateway
↓
读取 Authorization Token
↓
校验 JWT
↓
校验 Redis 登录态
↓
校验接口权限
↓
通过后转发到具体微服务
如果 Token 无效,Gateway 可以直接返回:
401 Unauthorized
请求根本不会进入后面的 Controller。
这就是 Gateway 作为统一入口的价值。
六、微服务有了 Gateway,还需要 Spring Security 吗?
需要看场景,但很多企业项目里仍然会保留。
更合理的设计是:
Gateway 做第一层入口校验
具体微服务内部做第二层业务权限控制
Gateway 适合做粗粒度校验:
用户有没有登录?
Token 是否有效?
这个接口是否需要登录?
请求是否被限流?
是否在黑名单中?
但是具体业务权限,很多时候还是要在服务内部判断。
比如订单服务中:
用户能不能查看这个订单?
这个订单是不是当前用户的?
当前用户有没有商户权限?
当前用户有没有管理员权限?
当前用户能不能导出订单?
这些判断和业务强相关,Gateway 不一定知道。
所以微服务中常见的安全结构是:
App / Web
↓
Gateway
↓
统一认证、限流、路由
↓
order-service
↓
Spring Security / Method Security
↓
OrderController
↓
OrderService
也就是说:
Gateway 管入口
Spring Security 管服务内部安全
不是有了 Gateway,后面的服务就可以完全裸奔。
七、那单体能不能用 Gateway?
能用。
单体也可以这样部署:
App / Web
↓
Nginx
↓
Gateway
↓
Spring Boot 单体应用
但是大多数普通单体项目没有必要这么做。
因为单体只有一个后端应用,没有多个服务需要路由转发。
很多事情在单体内部就可以完成:
认证鉴权:Spring Security
接口拦截:Filter / Interceptor
日志记录:AOP / Filter
跨域处理:Spring MVC / Nginx
限流控制:Nginx / Redis / Sentinel
如果只是一个普通后台系统,使用:
Nginx + Spring Boot + Spring Security
通常已经够了。
单体强行加 Gateway,反而会增加部署复杂度。
八、最终怎么判断要不要 Gateway?
可以按下面几个问题判断。
1. 后端是不是只有一个 Spring Boot 应用?
如果是,通常不需要 Gateway。
App
↓
Nginx
↓
Spring Boot
↓
Spring Security
↓
Controller
2. 后端是不是拆成了多个独立服务?
如果是,通常需要 Gateway。
App
↓
Gateway
↓
user-service / order-service / pay-service
3. 是否需要统一入口治理?
如果需要,可以考虑 Gateway。
比如:
统一鉴权
统一限流
统一日志
统一跨域
统一灰度
统一黑白名单
统一路由
4. 是否只是普通单体后台?
如果只是普通单体后台,Spring Security 基本够用。
九、最终总结
单体应用和微服务最大的区别,不是 Controller 和 Service 数量多少,而是是不是多个独立进程。
单体应用虽然也有很多 Controller 和 Service,但它们都在同一个 Spring Boot 进程中。
所以单体应用中:
请求进入 Spring Boot
↓
Spring Security 做认证鉴权
↓
Spring MVC 分发到 Controller
↓
Controller 调用 Service
微服务架构中,每个服务都是独立进程。
所以微服务中:
请求进入 Gateway
↓
Gateway 做统一入口治理
↓
Gateway 根据路径转发到具体微服务
↓
微服务 Controller 处理请求
最终可以记住这句话:
Gateway 解决的是多个服务的统一入口和路由转发问题;
Spring Security 解决的是当前应用内部的认证授权问题。
再压缩一下:
单体:
Nginx → Spring Boot → Spring Security → Controller
微服务:
Nginx → Gateway → 微服务 → Controller
所以不是所有后端接口都必须先经过 Gateway。
更准确的理解是:
单体应用通常以 Spring Security 为主;
微服务架构通常以 Gateway 作为统一入口,同时各个服务内部仍然可以保留 Spring Security 做业务权限保护。