【开发技巧/经验分享】在Zuul网关服务中实现限流、用户鉴权(访问鉴权) 、跨域访问

1. 在zuul网关服务中实现限流

1.1 为什么需要限流?

限流是为了保证服务器的负载量处于正常状态,因为如果太多的访问量可能会直接导致服务的崩溃。

1.2 如何实现限流

1.2.1 令牌桶算法简介

在zuul中实现限流是通过创建一个filter实现的,底层算法采用的是令牌桶算法,令牌桶会每秒往桶中投放一定数量的令牌,如果令牌桶中的令牌达到了临界值,新产生的令牌就会丢弃,当请求到达zuul网关后,会向令牌桶中提取令牌,如果成功拿到令牌则放行请求,如果没有拿到令牌则直接拒绝。
在这里插入图片描述

1.2.2 编码实现限流过滤器
package com.qingyun.apigetaway.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import com.qingyun.apigetaway.exception.RateLimterException;
import org.springframework.stereotype.Component;

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;

@Component
public class RateLimiterFilter extends ZuulFilter {

    //限流桶实现 每秒钟100个令牌
    private static final RateLimiter RATELIMITER=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 {

        if(!RATELIMITER.tryAcquire()){//判断是否取到令牌 
            //没有取到令牌 直接抛出异常
            throw new RateLimterException();
        }

        return null;
    }
}

这里需要注意的是,限流过滤器要放在其他所有过滤器之前。

2. 在zuul网关服务中实现用户鉴权(访问鉴权)

2.1 为什么需要鉴权操作?

鉴权操作在每一个项目中都是必须要具备的一个功能,因为在每一个项目中都会有敏感数据,一些比较重要的数据有具有相关权限的用户才能够访问,比如在一个点餐项目中,普通用户只能够访问商品的信息,而不能对商品信息进行修改,而对于卖家(管理员),它即可访问商品信息,也可以对商品信息进行修改。

2.2 如何实现访问鉴权?

2.2.1 首先搭建SpringCloudBus、StringCloudConfigClient用于动态更新访问路径,如果尚未搭建zuul可以查看我的另一篇博客:使用zuul构建ApiGetaway网关服务
2.2.1.1 导入配置客户端、事件消息总线、redis所需的maven依赖
<dependencies>

    <!--
    省略部分maven依赖
    -->
    
    <!--SpringCloudConfig配置客户端-->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-config</artifactId>
     </dependency>
     <!--SpingCloudBus事件消息总线 用户动态更新配置信息-->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-bus-amqp</artifactId>
     </dependency>
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>
     <!--用于验证用户信息-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
 </dependencies>
2.2.1.2 编写相关配置 bootstrap.yml
server:
  port: 8001
spring:
  application:
    name: apiGetaway
  cloud:
    config:
      discovery:
        enabled: true #开启配置中心发现
        service-id: config-server #配置中心的名称
      profile: dev
    bus:
      id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}
  profiles:
    active: dev
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
2.2.2 编写路径授权配置类
package com.qingyun.apigetaway.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: 李敷斌.
 * Date: 2020-02-18
 * Time: 21:43
 * Explain: 路径授权配置类
 */
@Component
@ConfigurationProperties(prefix = "auth-request")
@RefreshScope
@Data
public class AuthPathConfig {

    private List<String> buyerPaths;

    private List<String> sellerPaths;

    private List<String> commonPaths;
}
2.2.3 在git远程仓库中添加相关配置信息
auth-request:
  buyerPaths: #买家可访问的路径 ${order.server.name}是一个spel表达式用于获取order服务的名称
    - /${order.server.name}/order/create
    - /myOrder/order/create
  sellerPaths: #卖家可访问的路径
    - /${order.server.name}/order/finish
    - /myOrder/order/finish
  commonPaths: #公共路径
    - /${product.server.name}/product/list
    - /myProduct/product/list
2.2.4 编写买家访问授权过滤器、卖家访问授权过滤器

我们首先约定cookie中带有openid的为买家,cookie中带有token的为卖家。

  • 买家访问授权过滤器
package com.qingyun.apigetaway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.qingyun.apigetaway.config.AuthPathConfig;
import com.qingyun.apigetaway.utils.CookieUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

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;

/**
 * Created with IntelliJ IDEA.
 * User: 李敷斌.
 * Date: 2020-02-18
 * Time: 20:12
 * Explain: 买家鉴权过滤器
 */
@Component
public class AuthBuyerFilter extends ZuulFilter {

    @Autowired
    private AuthPathConfig authPathConfig;

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

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

    @Override
    public boolean shouldFilter() {

        RequestContext currentContext = RequestContext.getCurrentContext();

        HttpServletRequest request = currentContext.getRequest();

        String uri=request.getRequestURI();

        if(authPathConfig.getBuyerPaths().contains(uri)||authPathConfig.getCommonPaths().contains(uri)){
            return true;
        }

        return false;
    }

    @Override
    public Object run() throws ZuulException {

        RequestContext currentContext = RequestContext.getCurrentContext();

        HttpServletRequest request = currentContext.getRequest();

        String openid = CookieUtil.getValue("openid");

        if(StringUtils.isBlank(openid)){
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }

        return null;
    }
}
  • 卖家访问授权过滤器,在这里还需要通过token查询redis进行身份认证
package com.qingyun.apigetaway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.qingyun.apigetaway.config.AuthPathConfig;
import com.qingyun.apigetaway.constants.TokenConstant;
import com.qingyun.apigetaway.utils.CookieUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

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;

/**
 * Created with IntelliJ IDEA.
 * User: 李敷斌.
 * Date: 2020-02-18
 * Time: 20:12
 * Explain: 买家鉴权过滤器
 */
@Component
public class AuthSellerFilter extends ZuulFilter {

    @Autowired
    private AuthPathConfig authPathConfig;

    @Autowired
    private RedisTemplate redisTemplate;

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

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

    @Override
    public boolean shouldFilter() {

        RequestContext currentContext = RequestContext.getCurrentContext();

        HttpServletRequest request = currentContext.getRequest();

        String uri=request.getRequestURI();

        if(authPathConfig.getSellerPaths().contains(uri)||authPathConfig.getCommonPaths().contains(uri)){
            return true;
        }

        return false;
    }

    @Override
    public Object run() throws ZuulException {

        RequestContext currentContext = RequestContext.getCurrentContext();

        HttpServletRequest request = currentContext.getRequest();

        String token = CookieUtil.getValue(TokenConstant.TOKEN_KEY);

        if(StringUtils.isEmpty(token)||
                StringUtils.isEmpty(redisTemplate.opsForValue().get(String.format(TokenConstant.TOKEN_TEMPLATE,token)).toString())){
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }

        return null;
    }
}

以上代码编写完毕后就是可以实现访问路径控制了,授权的路径也可以实现动态更新。

3. zuul实现跨域访问

3.1 为什么需要实现跨域访问?

因为浏览器的同源策略的原因,会导致与当前访问的域名不一致的资源无法正常访问,要想对这些不同源的资源访问,那么必须做特殊处理。

3.2 实现跨域访问的方式有哪些?

前端使用Jsonp,还可以通过配置代理服务器的方式访问,也可以通过zuul服务网关对请求进行处理。

3.3 使用zuul实现跨域访问的具体步骤

3.3.1 编写一个跨域配置类
package com.qingyun.apigetaway.config;

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

import java.util.Arrays;

/**
 * Created with IntelliJ IDEA.
 * User: 李敷斌.
 * Date: 2020-02-19
 * Time: 11:51
 * Explain: zuul跨域访问配置类
 * C-Cross O-Origin R-Resource S-Sharing
 */
@Configuration
public class CorsConfig {

    public CorsFilter corsFilter(){
        final UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();


        CorsConfiguration config=new CorsConfiguration();

        config.setAllowCredentials(true); //允许跨域cookie
        config.setAllowedOrigins(Arrays.asList("*")); //设置原始域名
        config.setAllowedHeaders(Arrays.asList("*"));
        config.setAllowedMethods(Arrays.asList("*"));
        config.setMaxAge(300L);

        source.registerCorsConfiguration("/**",config);

        return new CorsFilter(source);
    }
}

配置完成后就可以实现跨域资源访问了,其他跨域实现方法还可以参考我的另一篇博文:SpringBoot实现跨域资源访问

发布了132 篇原创文章 · 获赞 55 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43199016/article/details/104391086
今日推荐