SpringCloud(三) 微服务安全实战 Zuul网关安全

微服务架构下的问题:

    安全处理和业务逻辑耦合,增加了复杂性和变更成本

    随着业务节点增加,认证服务器压力增大

    多个微服务同时暴露,增加了外部访问的复杂性

网关解决方案:

网关服务代码:

POM.xml:
        <!--         zuul网关相关       -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
        <!--            end            -->
        <!--         限流相关       -->
        <dependency>
            <groupId>com.marcosbarbero.cloud</groupId>
            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>
        <!--            end            -->

    限流配置,网关配置:

qpplicatoin.yml:
zuul:
  routes:
    token:
      url: http://localhost:9090
    order:
      url: http://localhost:9080
#  敏感头 设置为空,所有请求都往后转发
  sensitive-headers:
#    限流配置
  ratelimit:
    enabled: true
    repository: JPA  #REDIS
    default-policy-list:   #默认限流策略
      - limit: 2  #请求数
        quota: 2  #加在一起请求的时间
        refresh-interval: 1  #1秒钟之内可以有几个请求
        type:  #根据什么来处理流量
          - url /a get
          - httpmethod
    policy-list:  #根据路由规则限流
      token:
        - limit: 2
          quota: 2
          refresh-interval: 1
          type:
spring:
  qpplication:
    name: gateway
  datasource:
    url: jdbc:mysql://localhost:3306/imooc-security?characterEncoding=utf-8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  jpa:
    generate-ddl: true
    show-sql: true

server:
  port: 9070

    启动类:

/**
 * @author aric
 * @create 2021-04-07-17:59
 * @fun
 */
@SpringBootApplication
@EnableZuulProxy
public class GatewayServer {
    public static void main(String[] args) {
        SpringApplication.run(GatewayServer.class,args);
    }
}

    Token信息:

/**
 * @author aric
 * @create 2021-04-07-18:22
 * @fun
 */
@Data
public class TokenInfo {
    private boolean active;
    private String client_id;
    private String[] scope;
    private String user_name;
    //权限数组
    private String[] aud;
    //过期时间
    private Date exp;
    //用户所有的权限
    private String[] authorities;
}

三个(认证,日志,授权)网关配置:

    认证code:

/**
 * @author aric
 * @create 2021-04-07-18:07
 * @fun
 */
@Component
@Slf4j
public class OAuthFilter extends ZuulFilter {

    private RestTemplate restTemplate = new RestTemplate();

    //是否过滤
    @Override
    public boolean shouldFilter(){
        return true;
    }

    //业务逻辑
    @Override
    public Object run(){
        log.info("oauth start");
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        if(StringUtils.startsWith(request.getRequestURI(),"/token")){
            //发往认证服务器的请求不需要认证
            return null;
        }
        //别的请求
        String authHeader = request.getHeader("Authorization");
        //没带请求头
        if(StringUtils.isBlank(authHeader)){
            return null;
        }
        //不是bearer开头的不用处理
        if(!StringUtils.startsWithIgnoreCase(authHeader,"bearer ")){
            return null;
        }
        try{
            TokenInfo info = getTokenInfo(authHeader);
            request.setAttribute("tokenInfo",info);
        } catch (Exception e){
            log.error("get token info fail",e);
        }
        return null;
    }

    private TokenInfo getTokenInfo(String authHeader) {
        //去掉头部信息,拿到token
        String token = StringUtils.substringAfter(authHeader,"bearer ");
        String oauthServiceUrl = "http://localhost:9090/oauth/check_token";
        //发请求
        HttpHeaders headers = new HttpHeaders();
        //规定发送的是表单
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //设置请求头BasicAuth信息
        headers.setBasicAuth("gateway","123465");
        //请求参数设置
        MultiValueMap<String,String> params = new LinkedMultiValueMap<String, String>();
        params.add("token",token);
        //设置返回实体
        HttpEntity<MultiValueMap<String,String>> entity = new HttpEntity<MultiValueMap<String,String>>(params,headers);
        ResponseEntity<TokenInfo> response = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST,entity,TokenInfo.class);
        log.info("token info :" + response.getBody().toString());
        return response.getBody();
    }

    //过滤器类型,在之前or 之后 or 错误 or rout 时执行run方法
    @Override
    public String filterType(){
        return "pre";
    }

    //控制过滤器的执行顺序
    @Override
    public int filterOrder(){
        return 1;
    }

}

    日志code:

/**
 * @author aric
 * @create 2021-04-07-18:39
 * @fun
 */
@Component
@Slf4j
public class AuditLogFilter extends ZuulFilter {

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

    @Override
    public Object run(){
        log.info("audit log insert");
        return null;
    }

    @Override
    public String filterType(){
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 2;
    }

}

    授权code:

/**
 * @author aric
 * @create 2021-04-07-18:43
 * @fun
 */
@Component
@Slf4j
public class AuthorizationFilter extends ZuulFilter {

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

    @Override
    public Object run(){
        log.info("authorization start");
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        //判断当前请求需要认证否?
        if(isNeedAuth(request)){
            TokenInfo tokenInfo = (TokenInfo)request.getAttribute("tokenInfo");
            if(tokenInfo != null && tokenInfo.isActive()){
                //拿到用户信息,判断是否有权限
                if(!hasPermission(tokenInfo,request)){
                    log.info("audit log update fail 403");
                    handlerError(403,requestContext);
                }
                requestContext.addZuulRequestHeader("username",tokenInfo.getUser_name());
            }else{
                //拿不到认证信息
                if(!StringUtils.startsWith(request.getRequestURI(),"/token")) {
                    log.info("audit log update fail 401");
                    handlerError(401, requestContext);
                }
            }
        }
        return null;
    }

    private boolean hasPermission(TokenInfo tokenInfo, HttpServletRequest request) {
        //随机50%成功
        return RandomUtils.nextInt()%2 == 0;
    }

    private void handlerError(int status, RequestContext requestContext) {
        requestContext.getResponse().setContentType("application/json");
        requestContext.setResponseStatusCode(status);
        requestContext.setResponseBody("{\"message\":\"auth fail\"}");
        requestContext.setSendZuulResponse(false);
    }

    private boolean isNeedAuth(HttpServletRequest request) {
        return true;
    }

    @Override
    public String filterType(){
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 3;
    }
}

扩展:

    自定义限流处理规则:

/**
 * @author aric
 * @create 2021-04-07-21:04
 * @fun  自定义限流规则
 */
@Component
public class MyKeyGen extends DefaultRateLimitKeyGenerator {

    public MyKeyGen(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
        super(properties, rateLimitUtils);
    }

    @Override
    public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy){
        //在这里实现自己的限流规则
        return super.key(request,route,policy);
    }
}

    异常记录:

/**
 * @author aric
 * @create 2021-04-07-21:08
 * @fun 异常记录
 */
@Slf4j
public class MyRateLimitHandler extends DefaultRateLimiterErrorHandler {

    //往存储里存的时候的异常
    @Override
    public void handleSaveError(String key, Exception e) {
        log.error("Failed saving rate for " + key + ", returning unsaved rate", e);
    }

    @Override
    //往存储里取的时候的异常
    public void handleFetchError(String key, Exception e) {
        log.error("Failed retrieving rate for " + key + ", will create new rate", e);
    }

    @Override
    //限流过程中发生错误异常处理
    public void handleError(String msg, Exception e) {
        log.error(msg, e);
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值