一 实现访问鉴权的功能
思想
通过实现zuul的过滤器,检查访问是否带有token参数,如果有就进行路由,如果没有,就拒绝访问,返回401的错误
1 继承ZuulFilter的抽象类并实现4个抽象函数,就可以完成上述功能。
public class AccessFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext ctx=RequestContext.getCurrentContext();
HttpServletRequest request=ctx.getRequest();
Object accessToken= request.getParameter("accessToken");
if(accessToken==null) {
System.out.println("accessToken is faile!" );
//不对其进行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
System.out.println("accessToken is ok!" );
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public String filterType() {
return "pre";
}
}
filterType:过滤器类型,pre代表在路由请求之前执行
filterOrder:过滤器执行顺序,数字越小,优先级越高
shouldFilter:该过滤器是否应该被执行
run:执行的具体逻辑
2 实现了自定义过滤器,还需要创建bean才能启动该过滤器,并使用@EnableZuulProxy 注解开启Zuul的API网关服务功能,
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
3 启动该项目,访问http://localhost:5555/api-b-url/hello,控制台打印出accessToken is faile!,访问http://localhost:5555/api-b-url/hello?accessToken=111,就可以正常访问。
二 过滤器的生命周期有四个
pre:路由之前被调用
routing:在路由请求的时候被调用
post:在routing和error后被调用
error:处理请求的时候发送错误时被调用
其中代码如下:
try{
preRoute();
}catch(Exception ex){
error(ex);
postRoute();
return ;
}
try{
route();
}catch(Exception ex){
error(ex);
postRoute();
return ;
}
try{
postRoute();
}catch(Exception ex){
error(ex);
return ;
}
二 关于异常的处理
1 创建一个pre类型的过滤器,并在run方法中跑出异常,添加@Component注解,让spring可以创建该实例
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
@Component
public class ThrowExceptionFilter extends ZuulFilter {
@Override
public Object run() {
doSomething();
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public String filterType() {
return "pre";
}
private void doSomething() {
throw new RuntimeException("Exist some errors");
}
}
2 访问http://localhost:5555/api-b-url/hello?accessToken=111 地址,会发现一片空白,啥异常都没有输出,如下图
3 上述结果是因为有一个post类型的过滤器中,有一个SendErrorFilter过滤器是用来处理异常的,没有异常说明,这个过滤器没有被执行,该过滤器被执行的条件是请求的上下文必须有error.status_code参数,而我们ThrowExceptionFilter并没有设置这个参数,自然就不会进入该过滤器。把run方法改为如下,再次访问相同的地址,会发现界面有异常信息输出
public Object run() {
System.out.println("ThrowExceptionFilter run before");
RequestContext ctx=RequestContext.getCurrentContext();
try {
doSomething();
}catch(Exception ex) {
ctx.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception",ex);
}
System.out.println("ThrowExceptionFilter run after");
return null;
}
4 上述方法有个问题,需要try-catch来处理业务,并设置error.status_code的值,有的时候可能因为其他因素,依然会使得一些异常从过滤器中抛出,并没有try-catch来处理,这个时候可以用到error类型的过滤器来处理,定义一个error类型的过滤器,再次使用没有try-catch的ThrowExceptionFilter
@Component
public class ErrorExceptionFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext ctx=RequestContext.getCurrentContext();
Throwable t=ctx.getThrowable();
ctx.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception",t.getCause());
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return 10;
}
@Override
public String filterType() {
return "error";
}
}
访问http://localhost:5555/api-b-url/hello?accessToken=111,界面上会有异常打印出出来。
5 上述方法会有不足,因为post阶段调用的过滤器抛出异常后,经过error过滤器处理后,就没其他类型的过滤器来处理了。上述两种方法,都设置了error.status_code的参数,而这些参数起作用的是在post类型的SendErrorFilter过滤器,但是post类型的过滤器报错后,只会执行error类型过滤器,不会执行post类型过滤器,所以页面没有任何输出,将filterType方法的pre改为post。
@Component
public class ThrowExceptionFilter extends ZuulFilter {
@Override
public Object run() {
System.out.println("ThrowExceptionFilter run before");
RequestContext ctx=RequestContext.getCurrentContext();
doSomething();
System.out.println("ThrowExceptionFilter run after");
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public String filterType() {
return "post";
}
private void doSomething() {
throw new RuntimeException("Exist some errors");
}
}
访问地址http://localhost:5555/api-b-url/hello?accessToken=111,界面是一片空白
6 由于是因为post类型的过滤器发生了异常,执行error类型的过滤器,但是没有执行post类型的SendErrorFilter过滤器,我们只需要在error类型的过滤器里面执行SendErrorFilter过滤器的run方法即可,由于任何类型的过滤器发生异常都会导致执行error类型过滤器,所以在error过滤器里面需要判断是仅是post阶段发生的异常就有效。但是如何知道是哪个阶段发生的异常呢,查看源码发现FilterProcessor的processZuulFilter(ZuulFilter filter),该方法定义了用来执行filter的具体逻辑,需要继承FilterProcessor类,重写该方法,增加异常捕获,如果发生异常,就把过滤器类型字段放到请求的上下文中。
public class DidiFilterProcessor extends FilterProcessor {
@Override
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
try {
return super.processZuulFilter(filter);
}catch(Exception ex) {
RequestContext ctx=RequestContext.getCurrentContext();
ctx.set("failed.filter",filter);
throw ex;
}
}
}
7 继承SendErrorFilter类,仅对于post阶段抛出的异常有效
@Component
public class ErrorExtFilter extends SendErrorFilter {
@Override
public boolean shouldFilter() {
RequestContext ctx=RequestContext.getCurrentContext();
System.out.println("ErrorExtFilter run before");
ZuulFilter failedFilter=(ZuulFilter)ctx.get("failed.filter");
if(failedFilter!=null&&failedFilter.filterType().equals("post") ) {
Throwable t=ctx.getThrowable();
ctx.set("error.status_code",HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception",t.getCause());
return true;
}
return false;
}
@Override
public int filterOrder() {
return 30;
}
@Override
public String filterType() {
return "error";
}
}
8 扩展的过滤器处理类还没生效,需要在主类中FilterProcessor.setProcessor(new DidiFilterProcessor());重新设置过滤器处理类。
public class ApiGatewayApplication {
public static void main(String[] args) {
FilterProcessor.setProcessor(new DidiFilterProcessor());
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
三 禁用过滤器,可以禁用自定义的过滤器,也可以禁用默认的核心过滤器
zuul.<SimpleClassName>.<filterType>.disable=true
SimpleClassName:类名,比如上述用到的AccessFilter
filterType:过滤器类型,比如pre,post等
zuul:
AccessFilter:
pre:
disable: true