每个客户端用户请求微服务应用提供的接口时,它们权限往往都有一定的限制,系统并不会将所有的微服务接口都对它们开放。然而服务路由并没有限制权限这样的功能,所有请求被毫无保留的转发到具体应用并返回结果,为了实现对客户端请求的安全校验签名和权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一套用于校验签名和鉴别权限的的过滤器或拦截器。不过这样会增加日后的维护难度,而且代码也变得冗余。所以最好的做法是将这些检验剥离出去,构建一个独立的鉴权服务。
那么更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问系统已经有了统一入口,既然这些检验与具体业务无关,那就在请求到达的时候完成检验和过滤,而不是转发后再过滤导致更长的请求延迟,同时,通过在网关中完成校验和过滤,微服务应用端就可以去除各种复杂的国旗和拦截器,这样使得微服务应用接口的开发和测试复杂度也降低。
为了在API网关中实现对客户端请求的检验。Spring Cloud Zuul另外一个核心功能就是请求过滤。实现非常简单是许集成ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求拦截和过滤了。
ZuulFilter是在zuul-core-1.3.0.jar。
4个抽象函数ZuuFilter里有两个,IZuulFilter里面有两个,具体源码如下。
ZuuFilter部分源码:
public abstract class ZuulFilter implements IZuulFilter, Comparable {
private final DynamicBooleanProperty filterDisabled =
DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
*//抽象方法
* @return A String representing that type
*/
abstract public String filterType();
/**
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
* //抽象方法
* @return the int order of a filter
*/
abstract public int filterOrder();
IZuulFilter源码
public interface IZuulFilter {
/**
* a "true" return from this method means that the run() method should be invoked
*
* @return true if the run() method should be invoked. false will not invoke the run() method
*/
boolean shouldFilter();
/**
* if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
*
* @return Some arbitrary artifact may be returned. Current implementation ignores it.
*/
Object run();
}
Spring Cloud Zuul中实现的过滤器必选包含4个基本特征:过滤请求、执行顺序,执行条件、具体操作。这四个条件就是要继承ZuuFilter重写的四个方法。
具体各自的功能如下:
1.pre:可以在请求被路由之前调用
2.routing:在请求时被调用
3.post:在routing和error过滤之后调用。
4.error:处理请求时发生错误是被调用。
filterType 4个类型对于整个HTTP声明周期的控制图如下:
上面几个核心过滤器是在spring-cloud-netflix-core.jar文件下。
pom.xml
4.0.0
905.spring-cloud
zuul-filter
jar
0.0.1-SNAPSHOT
spring-cloud Maven Webapp
http://maven.apache.org
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
UTF-8
UTF-8
1.8
Dalston.RELEASE
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
启动类
package com.niugang;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import com.niugang.config.AccessFilter;
/**
* zuul启动类
*
* @author niugang
*
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
//可以直接用@SpringCloudApplication注解,该注解包含上面三个注解
/*
*设置一个Zuul服务器端点并在其中安装一些反向代理过滤器,这样它就可以将请求转发给后端服务器
*/
@EnableZuulProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
//配置filter
@Bean
public AccessFilter accessFilter(){
return new AccessFilter();
}
}
ZuulFilter
package com.niugang.config;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
public class AccessFilter extends ZuulFilter {
private final Logger logger = LoggerFactory.getLogger(AccessFilter.class);
@Override
public boolean shouldFilter() {
return true;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("send {} request to {} ",request.getMethod(),request.getRequestURL().toString());
String accessToken = request.getParameter("accessToken");
if(accessToken==null) {
logger.warn("access token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("no power");
return null;
}
logger.info("access token ok");
return null;
}
}
启动注册中心,服务提供者,
服务消费者,api网关
输入:http://localhost:8777/api-a/feign-consumer 显示
输入:http://localhost:8777/api-a/feign-consumer?accessToken=11 显示
微信公众号:
JAVA程序猿成长之路
分享资源,记录程序猿成长点滴。专注于Java,Spring,SpringBoot,SpringCloud,分布式,微服务。