微服务框架中网关提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流等。网关作为整个系统的访问入口,我们希望外部请求系统服务都需要通过网关访问,禁止通过ip端口直接访问,特别是一些重要的内部服务(外部无法直接访问的服务)
请求服务添加密钥传递验证,通过网关请求的服务会生成一串密钥,这个密钥会向下游服务彻底,下游服务在接收到请求的时候会先验证密钥的合法性,如未携带密钥或密钥不合法则拒绝响应,以此来达到避免各个微服务绕过网关被直接访问。
添加全局过滤器拦截处理,将密钥放入请求头中,键名为gatewayKey
/**
* 全局网关
*/
@Component
public class GatewayFilter implements GlobalFilter {
@Override
public Mono 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());
}
}
实现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");
}
}
实现RequestInterceptor接口,将请求放入请求头中,往下传递密钥
@Configuration
public class FeignConfiguration implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取request请求头信息,传递给下一层
Enumeration 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");
}
}
以上就是通过密钥校验的方式避免各个服务被直接访问的基本实现了。
上面的实现需要在每个微服务中实现,对于这部分重复的代码,可以抽象提取到公用服务模块,其他服务按需引入,是否开启网关拦截可通过注解控制。
@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在不慎暴露的时候(这个时候别人就能尝试请求的内部服务了),所以我们在这些底层服务添加了一层拦截,来鉴权访问者是否有权访问。
这种方案要妥善保管服务间通信的密钥,设置合适的加密规则和时效性。