目前咱们项目的架构图:前端
从上图中能够看到,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); } }