目录
- 1、概述
- 2、实现
- 3、网关模块
- 3.1 AbstractGatewayFilterFactory类
- 3.2 AbstractGatewayFilterFactory和 GlobalFilter区别
- 4、服务模块
- 5、服务之间请求传递请求头
- 6、 代码结构优化
1、概述
微服务框架中网关提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流等。网关作为整个系统的访问入口,我们希望外部请求系统服务都需要通过网关访问,禁止通过ip端口直接访问,特别是一些重要的内部服务(外部无法直接访问的服务)
我们要在项目中实现一个拦截器,需要继承两个类:GlobalFilter, Ordered
GlobalFilter:全局过滤拦截器,在gateway中已经有部分实现
Ordered:拦截器的顺序
于是一个简单的拦截器就有了
2、实现
在网关服务添加全局过滤器,拦截请求并将内部密钥设置到请求头中,这个密钥的规则可以选择合适的算法,我这里用的是字符串。
在内部服务实现Filter 接口,拦截接收到的请求,对密钥的合法性做校验,对合法请求放行并拒绝无效请求。
3、网关模块
添加全局过滤器拦截处理,将密钥放入请求头中,键名为gatewayKey
/**
* 全局网关
*/
@Component
public class GatewayFilter implements GlobalFilter , Ordered{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
PathContainer pathContainer = request.getPath().pathWithinApplication();
// 添加gatewayKey,防止下游接口直接被访问
ServerHttpRequest.Builder mutate = request.mutate();
mutate.header("gatewayKey", "key");
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
@Override
public int getOrder() {
return 0;
}
}
ServerWebExchange是Spring框架中的一个接口,用于表示HTTP请求和响应的上下文。它包含了请求和响应的所有信息,例如请求方法、请求头、请求体、响应状态码、响应头、响应体等。通过ServerWebExchange,可以对请求和响应进行操作和处理。
3.1 AbstractGatewayFilterFactory类
AbstractGatewayFilterFactory类中主要包含两个方法:createFilter()和apply()。
createFilter()方法:
该方法用于创建并返回一个GatewayFilter对象。在创建GatewayFilter对象时,可以传入一些配置参数,以定制化过滤器的行为。例如,可以在createFilter()方法中添加自定义的过滤逻辑,并将其封装到一个GatewayFilter对象中,然后将其返回。
示例代码:
java
public abstract GatewayFilter createFilter(Object... args) throws Exception;
apply()方法:
该方法用于将创建好的GatewayFilter对象应用到请求上。在apply()方法中,会接收一个ClientHttpRequestInterceptor对象作为参数,然后将其应用到请求上。这样,在请求被路由到目标服务之前或之后,就会先执行ClientHttpRequestInterceptor中定义的操作。
示例代码:
java
public abstract ClientHttpRequestInterceptor apply(Object... args);
通过实现AbstractGatewayFilterFactory类中的这两个方法,可以创建自定义的GatewayFilter,并将其应用到Spring Cloud Gateway中,以对请求和响应进行拦截和处理。
3.2 AbstractGatewayFilterFactory和 GlobalFilter区别
AbstractGatewayFilterFactory和GlobalFilter在Spring Cloud Gateway中都用于实现过滤功能,但它们之间存在一些区别。
作用范围:
GlobalFilter是一个全局过滤器,会应用于所有的路由请求。
AbstractGatewayFilterFactory是用于创建自定义GatewayFilter的抽象类,它封装了一些常见的过滤器配置逻辑,如添加参数、修改请求头等。
实现方式:
GlobalFilter通过实现GlobalFilter接口来在请求被路由到目标服务之前或之后执行一些操作,例如修改请求或响应,记录日志,添加头部信息等。它是全局性的,对所有的路由都起作用,无需为每个路由单独配置。
继承AbstractGatewayFilterFactory类并实现其中的方法可以创建自定义的GatewayFilter。这种方式提供了一种便捷的方式来创建自定义的GatewayFilter,封装了一些常见的过滤器配置逻辑。
总结来说,GlobalFilter是一个全局过滤器,应用于所有的路由请求;而AbstractGatewayFilterFactory是用于创建自定义GatewayFilter的抽象类,它封装了一些常见的过滤器配置逻辑。
4、服务模块
实现Filter接口,拦截所有请求,对所有请求的合法性做校验
/**
* 请求拦截,避免服务绕过接口被直接访问
*/
@Component
@WebFilter(filterName = "BaseFilter",urlPatterns = {"/user/**"})
public class BaseFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init filter");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("进入过滤器========");
HttpServletRequest request = (HttpServletRequest)servletRequest;
String gateway = request.getHeader("gatewayKey");
if(gateway == null || gateway.equals("") || !gateway.equals("key")){
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("destroy filter");
}
}
5、服务之间请求传递请求头
实现RequestInterceptor接口,将请求放入请求头中,往下传递密钥
@Configuration
public class FeignConfiguration implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取request请求头信息,传递给下一层
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);
}
}
// 独立设置参数
template.header("token","tokenKey");
}
}
以上就是通过密钥校验的方式避免各个服务被直接访问的基本实现了。
6、 代码结构优化
上面的实现需要在每个微服务中实现,对于这部分重复的代码,可以抽象提取到公用服务模块,其他服务按需引入,是否开启网关拦截可通过注解控制。
网关拦截注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({GatewayFilter.class})
@Inherited
public @interface EnableGatewayFilter {
}
public class GatewayFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("init gateway filter");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
String gateway = request.getHeader(GatewayFilterConstant.FILTER_KEY_NAME);
if(gateway == null || gateway.equals("") || !gateway.equals(GatewayFilterConstant.FILTER_KEY_SECRET)){
System.out.println("======无权访问=======");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
Filter.super.destroy();
System.out.println("destroy gateway filter");
}
}
密钥传递注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({CommunicationInterceptor.class})
@Inherited
public @interface EnableInnerCommunication {
}
public class CommunicationInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 独立设置参数
template.header(GatewayFilterConstant.FILTER_KEY_NAME,GatewayFilterConstant.FILTER_KEY_SECRET);
}
}
组合注解(网关了解+密钥传递)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@EnableInnerCommunication
@EnableGatewayFilter
public @interface EnableGatewayCommunication {
}
实际使用
@SpringBootApplication
@EnableDiscoveryClient
@EnableGatewayFilter
public class ServiceBasicApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceBasicApplication.class, args);
System.out.println("=========启动成功========");
}
}
这样就可以通过注解的方式灵活的设置服务是否必须通过网关访问。
思考总结
上述的方案在保障密钥安全的情况下,你的底层内部服务是不会被直接访问的
你也可以设置一定的加解密规则(MD5+时效校验,让密钥具有时效性),保障你的服务安全。
另外,可以对于内部服务,可以设置一定的URL规则,例如:/private/xxxService,网关统一拦截该/private/**类请求,这样外部在尝试访问内部服务的时候在网关就会被过滤掉
上述的方案是避免你的内部服务IP在不慎暴露的时候(这个时候别人就能尝试请求的内部服务了),所以我们在这些底层服务添加了一层拦截,来鉴权访问者是否有权访问。
这种方案要妥善保管服务间通信的密钥,设置合适的加密规则和时效性。