目前咱們項目的架構圖:前端
從上圖中能夠看到,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。以下:git
帶上token參數再測試一下,請求成功: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; } }
重啓項目,一樣訪問以前那個接口,測試結果以下:ajax
Zuul充當API網關的角色,全部的請求都通過它,因此很適合在其之上對API作限流保護,防止網絡×××。須要注意的是,用於限流的過濾器應該在請求被轉發以前調用,常見的限流算法有計數器、漏銅和令×××桶算法。redis
令×××桶算法示意圖:算法
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
以上咱們演示了pre、post過濾器的簡單使用,以及在Zuul上作限流,接下來咱們看看如何經過Zuul實現鑑權。一般來講,咱們鑑權的對象每每都是用戶,我這裏已經事先準備好了用戶服務以及相關接口。
需求,利用Zuul實現以下功能:
/** * /buyer/order/create 只能買家訪問 (cookie裏有openid) * /buyer/order/finish 只能賣家訪問 (cookie裏有token,而且redis存儲了session數據) * /buyer/product/list 均可以訪問 */
由於判斷用戶角色權限的時候,須要經過cookie和redis裏緩存的數據進行判斷,因此修改配置文件以下:
將以前作實驗的全部過濾器都註釋掉,而後新建一個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; } }
額外話題:
如今咱們的項目基本都是先後端分離的,前端經過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); } }