什么是网关
网关-网络的关口,门卫。数据从一个网络传输到另一个网络需要经过网关来做数据的路由和转发,以及数据安全的校验工作。从此以后,前端请求不再直接访问后端服务,而是访问网关(也是一个微服务)。
- 路由:选择数据要去的服务的目的地址
- 转发:转发数据给对应的服务
- 校验:在网关层面进行数据鉴权

SpringCloud提供了两种网关实现:
- Netflix Zuul:早期实现,目前已经淘汰
- SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强
响应式编程模型,即通过异步和非阻塞的方式处理数据流和事件。
依赖
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
|
配置
1 2 3 4 5 6 7 8 9 10 11 12
| gateway: routes: - id: item-service uri: lb://item-service predicates: - Path=/items/**,/search/** - id: cart-service uri: lb://cart-service predicates: - Path=/carts/** default-filters: - AddRequestParameter=red, blue
|
1 2 3 4 5
| @ApiOperation("分页查询商品") @GetMapping("/page") public PageDTO<ItemDTO> queryItemByPage(PageQuery query, @RequestParam("red") String res) { System.out.println(res);
|
JWT校验流程
- 用户登录后,用户微服务user-service使用预定义的jwt秘钥为用户生成一个JWT令牌(token),返回给客户端
- 用户之后对任何微服务的请求都必须带上这个token,服务端校验token(为空、篡改、过期等),成功后才执行请求的方法,返回数据。
使用网关进行登录校验
每个微服务都实现JWT校验逻辑未免太过复杂,而我们的网关正好会对所有的请求路由和转发,在网关层进行JWT校验在合适不过。
不过,这里存在几个问题:
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
| @Component @RequiredArgsConstructor @EnableConfigurationProperties(AuthProperties.class) public class AuthFilter 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) { ServerHttpRequest request = exchange.getRequest(); RequestPath path = request.getPath(); if (pathIsExclude(path)) { return chain.filter(exchange); } String token = null; List<String> headers = request.getHeaders().get("authorization"); if (headers != null && !headers.isEmpty()) { token = headers.get(0); } Long userId; try { userId = jwtTool.parseToken(token); } catch (UnauthorizedException e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-id", userId.toString())).build(); return chain.filter(swe); }
private boolean pathIsExclude(RequestPath path) { List<String> excludePaths = authProperties.getExcludePaths(); for (String pathPatten : excludePaths) { if (antPathMatcher.match(pathPatten, String.valueOf(path))) { return true; } } return false; }
@Override public int getOrder() { return 0; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component public class AuthInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userId = request.getHeader("user-id"); if (StrUtil.isNotBlank(userId)) { UserContext.setUser(Long.valueOf(userId)); } return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserContext.removeUser(); }
|
1 2 3 4 5 6 7 8 9 10
| @Bean RequestInterceptor feignRequestInterceptor() { return requestTemplate -> { Long userId = UserContext.getUser(); if (userId != null) { requestTemplate.header("user-id", userId.toString()); } }; }
|