服务网关和Zuul

服务网关

服务网关提供请求的统一入口,因此必须保证服务网关的稳定性和高可用,并且在性能、并发性方面好,确保服务安全性扩展性好。

1、 常用的网关方案有:

  • Nginx + Lua
  • Tyk
  • Kong
  • Spring Cloud Zuul

2、Zuul的特点

  • 路由 + 过滤器 = Zuul
  • 核心是一系列的过滤器

3、Zuul四种过滤器API

  • 前置过滤器(Pre)
  • 后置过滤器(Post)
  • 路由(route)
  • 错误(Error)

Zuul编码实例

1、创建一个Api-gateWay工程
2、导入下面依赖:

<dependencies>
<!-- 配置中心依赖-->
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-config</artifactId>
   </dependency>
   <!-- Eureka 客户端依赖-->
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
   </dependency>
   <!-- Zuul依赖-->
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
   </dependency>
</dependencies>

3、配置文件

spring:
  application:
    name: api-gateway
  cloud:
   config:
    discovery:
      enabled: true
      service-id: CONFIG
    profile: dev
eureka:
  client:
    service-url:
        defaultZone: http://localhost:8762/eureka

4、 在启动类中添加注解@EnableZuulProxy

@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {

   public static void main(String[] args) {
      SpringApplication.run(ApiGatewayApplication.class, args);
   }

5、测试
通过路由访问商品列表接口:http://localhost:9080/product/list

测试接口:http://localhost:8050/product/product/list

  • 第一个product代表服务名称
  • 后面的 product/list 表示资源路径

此时就可以进行路由转发基本功能。

此时必须通过服务名进行路由,下面通过配置进行自定义路由:

zuul:
  routes:
    myProduct:  #随意自定义
      path: /myProduct/**
      serviceId: product

如果想禁止某个路由被访问,可以进行如下配置:

zuul:
  # 排除某些路由
  ignored-patterns:
    - /**/product/list

zuul的Cookie传递

zuul默认的会对cookir进行过滤,我们只要在配置中将sensitiveHeaders设置为null即可

zuul:
  routes:
    myProduct:  #随意自定义
      path: /myProduct/**
      serviceId: product
      #  设置为null
      sensitiveHeaders:

此时在进行http请求,后台就可以获取到cookie.

动态路由

将配置部分配置到统一配置中心,在程序中想要获取属性值时,
我们可以通过添加Zuulconfig,创建ZuulProperties类。

/**
 *  动态路由 参数获取
 */
@Component
public class ZuulConfig {

    @ConfigurationProperties("zuul")
    @RefreshScope
     public ZuulProperties zuulProperties() {
         return new ZuulProperties();
     }
}

Pre和Post过滤器

1、pre过滤案例:所有请求都有一个token

@Component
public class TokenFilter  extends ZuulFilter{
    /**
     * 过滤类型设置
     * @return
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * 过滤顺序设置
     * @return
     */
    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    /**
     *  返回true
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        System.out.println("进来没.................");
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        // 这里从url参数里获取,也可以从cookie,header获取
        String token =  request.getParameter("token");
        if (StringUtils.isEmpty(token)) {
            // 返回沒有权限
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
}

2、Post过滤案例:对返回的数据做一些处理

@Component
public class AddResponseHeaderFilter  extends ZuulFilter{
    public AddResponseHeaderFilter() {
        super();
    }

    @Override
    public String filterType() {
        return POST_TYPE;
    }

    @Override
    public int filterOrder() {
        return 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;
    }
}

限流

限流采用的令牌桶算法,先将符合规定的令牌放入桶内,再从桶内取出令牌。

案例实现,Google已经实现了令牌桶算法。

/**
 *  限流 ,采用的令牌桶算法
 */
public class RateLimiterFilter  extends ZuulFilter{
      // 每秒中放100个令牌
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
    @Override
    public String filterType() {
        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 {
        // tryAcquire去取令牌
        // 判断没有取到令牌
        if (!RATE_LIMITER.tryAcquire()) {
           throw new RateLimitException();
        }
        return null;
    }
}

权限校验

根据不同的用户,访问不同的请求

package com.reder.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.reder.constant.RedisConstant;
import com.reder.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

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

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

/**
 *  权限拦截 (区分买家和卖家)
 */
@Component
public class AuthFilter extends ZuulFilter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

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

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        /**   依靠买家和卖家的特征,
         *   /order/create 只能买家访问(cookie 里有openid)
         *   /order/finish 只能卖家访问(cookie里有token,并且对应的redis中有值)
         *    /product/list 都可访问
         */
        if ("order/order/create".equals(request.getRequestURI())) {
            Cookie cookie = CookieUtil.get(request,"openid");
             if (cookie == null || StringUtils.isEmpty(cookie.getValue())) {
                 requestContext.setSendZuulResponse(false);
                 requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
             }
        }
        if ("order/order/finish".equals(request.getRequestURI())) {
             Cookie cookie = CookieUtil.get(request, "token");
             String redisValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_TEMPLATE,cookie.getValue()));
             if (cookie == null || StringUtils.isEmpty(cookie.getValue()) || StringUtils.isEmpty(redisValue)) {
                 requestContext.setSendZuulResponse(false);
                 requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
             }
        }
        return null;
    }
}

在如上代码中,我们对买家和卖家不同的请求,进行权限控制,虽然实现了我们需要的功能,但是代码比较臃肿,当需求发生变化时,改起来比较麻烦。我们可以做一下改动:

  • 将买家和卖家过滤请求拆分为两个类。
  • 在是否应该过滤中进行条件判断,run()方法只做逻辑处理。

买家过滤代码如下:

package com.reder.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.reder.constant.RedisConstant;
import com.reder.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

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

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

/**
 *  权限拦截 (区分买家和卖家), 买家
 */
@Component
public class AuthBuyerFilter extends ZuulFilter {
    private static final String BUYER_URI = "order/order/create";
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    /**
     * 是否应该拦截
     * @return  返回true为拦截
     */
    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        if (BUYER_URI.equals(request.getRequestURI())) {
            return true;
        }
        return false ;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        /**   依靠买家和卖家的特征,
         *   /order/create 只能买家访问(cookie 里有openid)
         *
         */
            Cookie cookie = CookieUtil.get(request,"openid");
             if (cookie == null || StringUtils.isEmpty(cookie.getValue())) {
                 requestContext.setSendZuulResponse(false);
                 requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
             }
        return null;
    }
}

卖家过滤代码如下:

package com.reder.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.reder.constant.RedisConstant;
import com.reder.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

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

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

/**
 *  权限拦截 (区分买家和卖家)
 */
@Component
public class AuthSellerFilter extends ZuulFilter {

    private static final String SELLER_URI = "order/order/finish";
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        if (SELLER_URI.equals(request.getRequestURI())) {
            return true;
        }
        return false ;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        /**   依靠买家和卖家的特征,
         *   /order/finish 只能卖家访问(cookie里有token,并且对应的redis中有值)
         */
             Cookie cookie = CookieUtil.get(request, "token");
             String redisValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_TEMPLATE,cookie.getValue()));
             if (cookie == null || StringUtils.isEmpty(cookie.getValue()) || StringUtils.isEmpty(redisValue)) {
                 requestContext.setSendZuulResponse(false);
                 requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
             }
        return null;
    }
}

跨域

ajax请求要符合同源策略,如果不符合同源策略就会发生跨域问题。
在解决跨域问题时,如果只有个别类和个别方法需要进行跨域请求,可以在该类或方法上添加@CrossOrigin注解,当整个服务都有跨域问题时,可以在zuul进行如下跨域配置:

package com.reder.config;

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

import java.util.Arrays;

/**
 *  跨域配置
 *
 */
@Component
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        //
        config.setAllowCredentials(true);
        // 配置原始路径 http:www.a.com
        config.setAllowedOrigins(Arrays.asList("*"));
        config.setAllowedHeaders(Arrays.asList("*"));
        config.setAllowedMethods(Arrays.asList("*"));
        config.setMaxAge(300L);

        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值