zuul實現Cors跨域的兩種方式(https)

kasaya發表於2019-01-19

大家都知道spring boot 可以通過@CrossOrigin實現跨域。但是在spring cloud 裡,如果要粒度那麼細的去控制跨域,這個就太繁瑣了,所以一般來說,會在路由zuul裡實現。

第一種方式:corsFilter

在zuul服務下新增一個corsFilter實現跨域,實現起來方便。程式碼如下

@Configuration
public class GateWayCorsConfig {
   @Bean
    public FilterRegistrationBean corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        //這個請求頭在https中會出現,但是有點問題,下面我會說
        //config.addExposedHeader("X-forwared-port, X-forwarded-host"); 
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
}

經過測試,這樣的配置在http的情況下跨域是OK的,但是當我的環境切換的https的情況下就發生了奇怪的問題。說明一下我遇到的問題。
前端 服務A後端服務B 在同一臺伺服器上,服務A 呼叫 服務B 時,服務A通過負載均衡進入服務B時:
http時,服務A的請求跨域成功,https時,服務A的請求跨域失敗。
也就是埠為443的時候,會被認為跨域失敗!!
我一開始對比了請求頭,以為是少了ExposedHeader的”X-forwared-port, X-forwarded-host”,但是新增後,還是失敗。因為急著上線,所以我沒有去深入測試到底什麼原因引起的https請求跨域失敗。(所以如果大家發現我哪裡寫的不對,請務必通知我,讓我也明白為什麼失敗!謝謝!)

第二種方式:繼承ZuulFilter

因為第一種方式在https下失敗後,我嘗試了用zuulfilter實現cors的方式
一共需要兩個filiter:一個pre, 一個post

Pre-Filter:

@Component
public class FirstFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(FirstFilter.class);

    @Override
    public String filterType() {
        /*
        pre:可以在請求被路由之前呼叫
        route:在路由請求時候被呼叫
        post:在route和error過濾器之後被呼叫
        error:處理請求時發生錯誤時被呼叫
        * */
        // 前置過濾器
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //// 優先順序為0,數字越大,優先順序越低
        return 0;
    }
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        //只過濾OPTIONS 請求
        if(request.getMethod().equals(RequestMethod.OPTIONS.name())){
            return true;
        }

        return false;
    }

    @Override
    public Object run() {
        logger.debug("*****************FirstFilter run start*****************");
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletResponse response = ctx.getResponse();
        HttpServletRequest request = ctx.getRequest();
        response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials","true");
        response.setHeader("Access-Control-Allow-Headers","authorization, content-type");
        response.setHeader("Access-Control-Allow-Methods","POST,GET");
        response.setHeader("Access-Control-Expose-Headers","X-forwared-port, X-forwarded-host");
        response.setHeader("Vary","Origin,Access-Control-Request-Method,Access-Control-Request-Headers");
        //不再路由
        ctx.setSendZuulResponse(false);
        ctx.setResponseStatusCode(200);
        logger.debug("*****************FirstFilter run end*****************");
        return null;
    }
}

Pre-Filter 用來處理預處理OPTIONS請求,當發現是OPTIONS請求的時候,給出跨域響應頭,並且不對其進行zuul路由,直接返回成功(200), 給前端服務允許跨域


post-Filter :

@Component
public class PostFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(PostFilter.class);

    @Override
    public String filterType() {
        /*
        pre:可以在請求被路由之前呼叫
        route:在路由請求時候被呼叫
        post:在route和error過濾器之後被呼叫
        error:處理請求時發生錯誤時被呼叫
        * */
        // 前置過濾器
        return FilterConstants.POST_TYPE;
    }

    @Override
    public int filterOrder() {
        //// 優先順序為0,數字越大,優先順序越低
        return 2;
    }
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        //過濾各種POST請求
        if(request.getMethod().equals(RequestMethod.OPTIONS.name())){
            return false;
        }
        return true;
    }

    @Override
    public Object run() {
        logger.debug("*****************PostFilter run start*****************");
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletResponse response = ctx.getResponse();
        HttpServletRequest request = ctx.getRequest();
        response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials","true");
        response.setHeader("Access-Control-Expose-Headers","X-forwared-port, X-forwarded-host");
        response.setHeader("Vary","Origin,Access-Control-Request-Method,Access-Control-Request-Headers");
        //允許繼續路由
        ctx.setSendZuulResponse(true);
        ctx.setResponseStatusCode(200);
        logger.debug("*****************PostFilter run end*****************");
        return null;
    }
}

Post-Filter 用來處理 預處理OPTIONS以外的請求,對於正常的請求,不但要給出跨域請求頭,還需要允許請求進行路由(否則你的請求到這兒就結束啦),然後返回狀態碼200。(emmmm……這裡要不要返回200,我覺得可能還要想一想……)

按照以上方式配置的話,方法一出現的問題,就得到了解決。服務A能夠正常請求服務B了

雖然是正常實現了需求,但是感覺還是存在很多疑惑,希望大家看到的話,能給我指出不足。一起討論!

相關文章