Spring Cloud Zuul 綜合使用

Zuul:Pre和Post過濾器

目前咱們項目的架構圖:
Spring Cloud Zuul 綜合使用前端

從上圖中能夠看到,Zuul是咱們整個系統的入口。當咱們有參數校驗的需求時,咱們就能夠利用Zuul的Pre過濾器,進行參數的校驗。例如我如今但願請求都一概帶上token參數,不然拒絕請求。在項目中建立一個filter包,在該包中新建一個TokenFilter勞累並繼承ZuulFilter,代碼以下:java

package org.zero.springcloud.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * @program: api-gateway
 * @description: token過濾器
 * @author: 01
 * @create: 2018-08-25 17:03
 **/
@Component
public class TokenFilter extends ZuulFilter {

    @Override
    public String filterType() {
        // 聲明過濾器的類型爲Pre
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 將這個過濾器的優先級放在 PRE_DECORATION_FILTER_ORDER 以前,數字越小優先級越高
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        // 開啓這個過濾器
        return true;
    }

    /**
     * 這個方法用於自定義過濾器的處理代碼
     *
     * @return Object
     * @throws ZuulException ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 從上下文中拿到請求對象
        HttpServletRequest request = requestContext.getRequest();

        // 拿出參數裏的token
        String token = request.getParameter("token");
        if (StringUtils.isEmpty(token)) {
            // 驗證失敗
            requestContext.setSendZuulResponse(false);
            // 返回401權限不經過
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }

        return null;
    }
}

重啓項目,咱們來訪問一個接口,不帶上token參數,看看是否會返回401。以下:
Spring Cloud Zuul 綜合使用git

帶上token參數再測試一下,請求成功:
Spring Cloud Zuul 綜合使用github

從以上的示例中,能夠看到利用Pre能夠對請求進行一些預處理。若是但願在請求處理完成後,對返回的數據進行處理的話。就須要使用的Post過濾器,例如咱們要在http返回頭中,加上一個自定義的X-Foo屬性。代碼以下:web

package org.zero.springcloud.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * @program: api-gateway
 * @description:
 * @author: 01
 * @create: 2018-08-25 17:10
 **/
@Component
public class AddResponseHeaderFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse response = requestContext.getResponse();
        response.setHeader("X-Foo", UUID.randomUUID().toString());

        return null;
    }
}

重啓項目,一樣訪問以前那個接口,測試結果以下:
Spring Cloud Zuul 綜合使用ajax


Zuul:限流

Zuul充當API網關的角色,全部的請求都通過它,因此很適合在其之上對API作限流保護,防止網絡×××。須要注意的是,用於限流的過濾器應該在請求被轉發以前調用,常見的限流算法有計數器、漏銅和令×××桶算法。redis

令×××桶算法示意圖:
Spring Cloud Zuul 綜合使用算法

Google開源工具包Guava提供了限流工具類RateLimiter,該類基於令×××桶算法(Token Bucket)來完成限流,很是易於使用。RateLimiter常常用於限制對一些物理資源或者邏輯資源的訪問速率,它支持兩種獲取permits接口,一種是若是拿不到馬上返回false,一種會阻塞等待一段時間看能不能拿到。spring

咱們來建立一個過濾器,簡單使用一下這個RateLimiter。代碼以下:數據庫

package org.zero.springcloud.apigateway.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import org.zero.springcloud.apigateway.exception.RateLimiterException;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_DETECTION_FILTER_ORDER;

/**
 * @program: api-gateway
 * @description: 限流過濾器
 * @author: 01
 * @create: 2018-08-25 21:04
 **/
@Component
public class RateLimiterFilter extends ZuulFilter {

    /**
     * 每秒鐘放入100個令×××
     */
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);

    @Override
    public String filterType() {
        // 限流確定是得在Pre類型的過濾器裏作
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 設置過濾器的優先級爲最高
        return SERVLET_DETECTION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 嘗試從令×××桶中獲取令×××
        if (!RATE_LIMITER.tryAcquire()) {
            // 獲取失敗拋出異常,或作其餘處理
            throw new RateLimiterException();
        }

        return null;
    }
}

除了這個RateLimiter以外,GitHub上也有一些開源的實現。我這裏發現了一個還不錯的,地址以下:

https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit


Zuul:完成權限校驗

以上咱們演示了pre、post過濾器的簡單使用,以及在Zuul上作限流,接下來咱們看看如何經過Zuul實現鑑權。一般來講,咱們鑑權的對象每每都是用戶,我這裏已經事先準備好了用戶服務以及相關接口。

需求,利用Zuul實現以下功能:

/**
 * /buyer/order/create 只能買家訪問 (cookie裏有openid)
 * /buyer/order/finish 只能賣家訪問 (cookie裏有token,而且redis存儲了session數據)
 * /buyer/product/list 均可以訪問
 */

由於判斷用戶角色權限的時候,須要經過cookie和redis裏緩存的數據進行判斷,因此修改配置文件以下:
Spring Cloud Zuul 綜合使用

將以前作實驗的全部過濾器都註釋掉,而後新建一個AuthBuyerFilter過濾器,用於攔截訂單建立的請求。代碼以下:

package org.zero.springcloud.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.zero.springcloud.apigateway.utils.CookieUtil;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

/**
 * @program: api-gateway
 * @description: 買家權限過濾器
 * @author: 01
 * @create: 2018-08-25 17:03
 **/
@Component
public class AuthBuyerFilter extends ZuulFilter {

    private static final String ORDER_CREATE = "/order/buyer/order/create";

    @Override
    public String filterType() {
        // 聲明過濾器的類型爲Pre
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 將這個過濾器的優先級放在 PRE_DECORATION_FILTER_ORDER 以前
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 從上下文中拿到請求對象
        HttpServletRequest request = requestContext.getRequest();

        // 若是訪問的是 ORDER_CREATE 則進行攔截,不然不進行攔截
        return ORDER_CREATE.equals(request.getRequestURI());
    }

    /**
     * 這個方法用於自定義過濾器的處理代碼
     *
     * @return Object
     * @throws ZuulException ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 從上下文中拿到請求對象
        HttpServletRequest request = requestContext.getRequest();

        // /buyer/order/create 只能買家訪問 (cookie裏有openid)
        Cookie cookie = CookieUtil.get(request, "openid");
        if (cookie == null || StringUtils.isBlank(cookie.getValue())) {
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }

        return null;
    }
}

接着再新建一個AuthSellerFilter過濾器,用於攔截訂單完結的請求。代碼以下:

package org.zero.springcloud.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.zero.springcloud.apigateway.constant.RedisConstant;
import org.zero.springcloud.apigateway.utils.CookieUtil;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

/**
 * @program: api-gateway
 * @description: 賣家權限過濾器
 * @author: 01
 * @create: 2018-08-25 17:03
 **/
@Component
public class AuthSellerFilter extends ZuulFilter {

    private final StringRedisTemplate redisTemplate;
    private static final String ORDER_FINISH = "/order/buyer/order/finish";

    @Autowired
    public AuthSellerFilter(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public String filterType() {
        // 聲明過濾器的類型爲Pre
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 將這個過濾器的優先級放在 PRE_DECORATION_FILTER_ORDER 以前
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 從上下文中拿到請求對象
        HttpServletRequest request = requestContext.getRequest();

        // 若是訪問的是 ORDER_FINISH 則進行攔截,不然不進行攔截
        return ORDER_FINISH.equals(request.getRequestURI());
    }

    /**
     * 這個方法用於自定義過濾器的處理代碼
     *
     * @return Object
     * @throws ZuulException ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 從上下文中拿到請求對象
        HttpServletRequest request = requestContext.getRequest();

        // /buyer/order/finish 只能賣家訪問 (cookie裏有token,而且redis存儲了session數據)
        if (ORDER_FINISH.equals(request.getRequestURI())) {
            Cookie cookie = CookieUtil.get(request, "token");
            if (cookie == null ||
                    StringUtils.isBlank(cookie.getValue()) ||
                    StringUtils.isNotBlank(redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue())))) {
                requestContext.setSendZuulResponse(false);
                requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            }
        }

        return null;
    }
}

額外話題:

  • 網關不要鏈接任何服務的關係型數據庫
  • 獲取數據應該經過調用服務接口的方式進行獲取
  • 常常須要獲取的數據有必要緩存到redis中,例如須要進行簡單的權限緩存

Zuul:跨域

如今咱們的項目基本都是先後端分離的,前端經過ajax來請求後端接口。因爲瀏覽器的同源策略,因此會出現跨域的問題。而在微服務架構中,咱們能夠在網關上統一解決跨域的問題。

在Zuul裏增長CorsFilter過濾器的配置類便可。代碼以下:

package org.zero.springcloud.apigateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * @program: api-gateway
 * @description: 跨域配置
 * @author: 01
 * @create: 2018-08-27 23:02
 **/
@Configuration
public class CorsConfig {

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 容許cookie跨域
        corsConfiguration.setAllowCredentials(true);
        // 容許任何域名使用
        corsConfiguration.addAllowedOrigin("*");
        // 容許任何頭
        corsConfiguration.addAllowedHeader("*");
        // 容許任何方法(post、get等)
        corsConfiguration.addAllowedMethod("*");
        // 設置跨域緩存時間,單位爲秒
        corsConfiguration.setMaxAge(300L);

        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 對接口配置跨域設置
        source.registerCorsConfiguration("/**", buildConfig());

        return new CorsFilter(source);
    }
}
相關文章
相關標籤/搜索