SpringCloud 入门(六) Gateway

Spring Cloud Gateway

1基础

1.1理论说明

网关旨在为微服务架构提供- 种简单而有效的统-的API路由管理方式。

●在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。

    ●存在的问题:

    ● 客户端多次请求不同的微服务,增加客户端的复杂性

    ●认证复杂,每个服务都要进行认证

    ●http请求不同服务次数增加, 性能不高

 

 

使用Gateway之后,用户之和网关打交道,不和服务打交道。解决了上面三个问题

●网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统-服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控、路由转发等

●在目前的网关解决方案里,有Nginx+ Lua、 Netlix Zuul、Spring Cloud Gateway等等

1.2快速入门

1.2.1 首先在原来项目中创建一个服务 ,名为Gateway;创建之后先只设置启动类和配置文件

1.2.2在pom.xml中引入相关依赖

   <dependencies>
<!--        引入Gateway相关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

<!--        因为后续要和eureka相关操作,此处引用eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

1.2.3编写配置文件

在Gateway服务的配置文件(application.yml)中(静态路由)

server:
  port: 8099

spring:
  application:
    name: backend-fateway
  cloud:
    gateway:
      routes:
        #添加第一个服务
        - id: backend-show-provider-fateway
          #静态路由
          uri: http://localhost:7010/
#配置访问地址规则,provider 是show-provider服务中的一个控制器
          predicates:
          - Path=/provider/**
      discovery:
        locator:
          enabled: true  #开启微服务发现功能
          lower-case-service-id: true # 请求上服务名配置为小写(因为erueka 默认为大写)

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
# 访问 http://localhost:8099/provider/sayhello2?message=2233  就会有  http://localhost:7010/provider/sayhello2?message=2233结果

配置好之后,通过访问【Gateway服务】的ip+【show-provider服务】的地址就能得到 正常访问【show-provider服务】的结果

配置静态访问就有一个问题,如果不同服务下有相同的Controller名称和 方法名;所以就需要考虑使用动态路由

server:
  port: 8099

spring:
  application:
    name: backend-fateway
  cloud:
    gateway:
      routes:
        #添加第一个服务
        - id: backend-show-provider-fateway
          #静态路由
#          uri: http://localhost:7010/
          #动态路由
          uri: lb://backend-show-provider
          predicates:
          - Path=/provider/**
      discovery:
        locator:
          enabled: true  #开启微服务发现功能
          lower-case-service-id: true # 请求上服务名配置为小写(因为erueka 默认为大写)

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
# 访问 http://localhost:8099/provider/sayhello2?message=2233  就会有  http://localhost:7010/provider/sayhello2?message=2233结果

 

 

 

1.3Gateway过滤器

1.3.1基础

●Gateway 支持过滤器功能,对请求或响应进行拦截,完成一些通用操作。

●Gateway 提供两种过滤器方式: "pre" 和"post"

●pre过滤器,在转发之前执行,可以做参数校验、权限校验、流量监控、日志输出、协议转换等。

●post过滤器,在响应之前执行,可以做响应内容、响应头的修改,日志的输出,流量监控等。

●Gateway还提供了两种类型过滤器

    ·GatewayFilter:局部过滤器,针对单个路由

    ·GlobalFilter :全局过滤器,针对所有路由

 

 

1.3.2局部过滤器

  1. GatewayFilter局部过滤器,是针对单个路由的过滤器。
  2. 在Spring Cloud Gateway组件中提供了大量内置的局部过滤器,对请求和响应做过滤操作。
  3. 遵循约定大于配置的思想,只需要在配置文件配置局部过滤器名称,并为其指定对应的值,就可以让其生效。

配置工厂:https://blog.csdn.net/abu935009066/article/details/112252692

 

1.3.3全局过滤器

 

GlobalFilter全局过滤器,不需要在配置文件中配置,系统初始化时加载,并作用在每个路由上。

Spring Cloud Gateway核心的功能也是通过内置的全局过滤器来完成

自定义全局过滤器步骤:

1.定义类实现GlobalFilter和Ordered接口

package com.item.bakend.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class MyFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        return null;
        System.out.println("全局过滤器");
        return chain.filter(exchange);//放行
    }

    /**
     * 过滤器排序,值越小-越先执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

2.复写方法

3.完成逻辑处理

 

 

 

2.Zuul

2.1基础

◆ Zuul是网关大军中的一员,目前市场使用规律比较高,

◆ Zuu|除了实现请求转发和过滤,一般还作为鉴权和容错使用

◆Zuul可以无缝衔接Ribbon和Hystrix

◆使用Zuul也是实现Gateway,请求方式 :

 格式:   http://localhost:Zuul服务的端口/配置的被访问服务名称/Controller名/方法名

demo:   http://localhost:8080/film-api/film/actors

请求路由

◆Zuul可以通过配置完成请求路由配置

◆Zuul服务路由默认支持serviceld作为上下文

◆ignored-services可以禁用serviceld

demo:被请求服务backend_film。

创建一个zuul服务,主要就是 myFilter.java和application.yml文件。

application.yml中配置zuul相关信息

server:
  port: 8080

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

zuul:
  routes:
    backend_film: #application.name 服务名称   请求方式1(不推荐-暴露服务名称 )=》http://localhost:8080/backend_film/film/actors  =》http://localhost:8080/backend_film +Controller 名称+方法名称  和下面效果一样
     path: /film-api/**   #匹配规则 请求路劲以 film-api开头 ,后面不管是什么,都会去请求backend_film 的内容 例如 http://localhost:8080/film-api/film/actors  :http://localhost:8080/film-api +Controller 名称+方法名称

logging:
  config: classpath:logback.xml

MyFilter.java配置过滤信息.一般可以做jwt判断等等

package com.item.backed.zuul.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

@Slf4j
@Component
public class MyFilter extends ZuulFilter {
    /**
     * @Description: Filter类型
     * @Param: []
     * @return: java.lang.String
     * @Author: jiangzh
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * @Description: filter的执行顺序
     * @Param: []
     * @return: int
     * @Author: jiangzh
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * @Description: 是否要拦截
     * @Param: []
     * @return: boolean
     * @Author: jiangzh
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * @Description: Filter的具体业务逻辑
     * @Param: []
     * @return: java.lang.Object
     * @Author: jiangzh
     */
    @Override
    public Object run() throws ZuulException {  //编写顾虑器拦截业务逻辑代码
        // 案例:拦截所有都服务接口,判断服务接口上是否有传递userToekn参数
        //获取上下文
        RequestContext currentContext = RequestContext.getCurrentContext();
        //获取request对象
        HttpServletRequest request = currentContext.getRequest();
        //验证token时候 token的参数 从请求头获取
        String userToken = request.getParameter("userToken");
        if (StringUtils.isEmpty(userToken)) {
            //返回错误提示
            currentContext.setSendZuulResponse(false);  //false  不会继续往下执行 不会调用服务接口了 网关直接响应给客户了
            currentContext.setResponseBody("you have not userToken");
            currentContext.setResponseStatusCode(401);
            return null;
        }
        //否则正常执行 调用服务接口...
        return null;
    }
//    public Object run() throws ZuulException {
//        // ThreadLocal
//        RequestContext requestContext = RequestContext.getCurrentContext();
//
//        HttpServletRequest request = requestContext.getRequest();
//
//        Enumeration<String> headerNames = request.getHeaderNames();
//        while (headerNames.hasMoreElements()){
//            String headName = headerNames.nextElement();
//            log.info("headName:{}, headValue:{}", headName, request.getHeader(headName));
//        }
//
//        return null;
//    }
}

application.yml的请求路由表达式 

◆?   ->匹配任意单个字符

◆*   ->配置任意数量的字符

◆**  ->配置任意数量的字符,支持多级目录.

补充---zuul prefix

#zuul:
#  routes:
#    backend_film: #application.name 服务名称   请求方式1(不推荐-暴露服务名称 )=》http://localhost:8080/backend_film/film/actors  =》http://localhost:8080/backend_film +Controller 名称+方法名称  和下面效果一样
#     path: /film-api/**   #匹配规则 请求路劲以 film-api开头 ,后面不管是什么,都会去请求backend_film 的内容 例如 http://localhost:8080/film-api/film/actors  :http://localhost:8080/film-api +Controller 名称+方法名称

zuul:
  prefix: "/meetingfilm/"  ## 统一前缀
  routes:
    meetingfilm-user:
      path: /userapi/**  #匹配规则
      serviceId: backend_user  #服务名称
      retryable: true   # 是否允许重试 , 饿汉模式
    backend_film:
      path: /filmapi/**    #匹配规则   例如  http://localhost:8080/meetingfilm/filmapi/film/actors
      serviceId: backend_film   #服务名称  
      retryable: true

2.2Zuul整合其他

2.2.1整合Hystrix

application.yml配置

server:
  port: 8080

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

zuul:
  routes:
    backend_film: #application.name 服务名称   请求方式1(不推荐-暴露服务名称 )=》http://localhost:8080/backend_film/film/actors  =》http://localhost:8080/backend_film +Controller 名称+方法名称  和下面效果一样
     path: /film-api/**   #匹配规则 请求路劲以 film-api开头 ,后面不管是什么,都会去请求backend_film 的内容 例如 http://localhost:8080/film-api/film/actors  :http://localhost:8080/film-api +Controller 名称+方法名称

logging:
  config: classpath:logback.xml

# hystrix配置
hystrix:
  command:
    default:
      excution:
        timeout:
          enable: true   #开启超时监控
        isolation:
          thread:
            timeoutInMilliseconds: 10  #超时监控  毫秒


然后添加一个class文件,继承FallbackProvider,同时注意使用 @Component 注解

package com.item.backed.zuul.fallbacks;

import com.alibaba.fastjson.JSONObject;
import com.item.backed.utils.common.vo.BaseResponseVO;
import com.item.backed.utils.exception.CommonServiceException;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 降级服务处理
 */
@Component
public class MyFallback implements FallbackProvider {
    /**
     * 针对哪一个路由进行降级, return可以写 *;backend_film是一个服务名,此处代表针对zuul中所有针对backend_film的请求都有熔断监控
     * @return
     */
    @Override
    public String getRoute() {
        return "backend_film";
    }

    /**
     * 降级时处理方式
     * @param route
     * @param cause
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            /**
             * @Description: 业务降级处理方式
             * 熔断之后 返回 404, "No backend_film!~"
             */
            @Override
            public InputStream getBody() throws IOException {
                BaseResponseVO responseVO
                        = BaseResponseVO.serviceException(
                        new CommonServiceException(404, "No backend_film!~"));
                String result = JSONObject.toJSONString(responseVO);
                return new ByteArrayInputStream(result.getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

 

微服务网关Zuul路由ZuulFilter过滤,创建一个class文件 继承ZuulFilter,然后在其中可以进行请求参数的获取以及拦截

package com.item.backed.zuul.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

@Slf4j
@Component
public class MyFilter extends ZuulFilter {
    /**
     * @Description: Filter类型
     * @Param: []
     * @return: java.lang.String
     * @Author: jiangzh
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * @Description: filter的执行顺序
     * @Param: []
     * @return: int
     * @Author: jiangzh
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * @Description: 是否要拦截
     * @Param: []
     * @return: boolean
     * @Author: jiangzh
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * @Description: Filter的具体业务逻辑
     * @Param: []
     * @return: java.lang.Object
     * @Author: jiangzh
     */
    @Override
    public Object run() throws ZuulException {
        // ThreadLocal
        RequestContext requestContext = RequestContext.getCurrentContext();

        HttpServletRequest request = requestContext.getRequest();
        String userToken = request.getHeader("userToken");
        if(StringUtils.isEmpty(userToken)){
            //返回错误提示
            RequestContext currentContext = RequestContext.getCurrentContext();
            currentContext.setSendZuulResponse(false);  //false  不会继续往下执行 不会调用服务接口了 网关直接响应给客户了
            currentContext.setResponseBody("you have not userToken");
            currentContext.setResponseStatusCode(401);
            return null;
        }
        String cookie = request.getHeader("Cookie");
        String header = request.getHeader("Set-Cookie");

        log.info("Object userToken:{}",userToken);
        log.info("Object cookie:{}",cookie);
        log.info("Object header:{}",header);

        //打印所有的头部参数 key、value
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String headName = headerNames.nextElement();
            //获取ing求头
            log.error("Object headName:{}, headValue:{}", headName, request.getHeader(headName));
        }

        return null;
    }
}

后台日志打印

通过ZuulFilter 完成jwt以及 Cors跨域

Cors解决跨域问题

package com.item.backed.zuul.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;

/**
 * @description : 解决跨域问题
 **/
@Component
public class CorsFilter extends ZuulFilter {

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

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

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

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // 跨域
        HttpServletResponse response = ctx.getResponse();
        response.addHeader("Access-Control-Allow-Origin", "*");//运行的请求来源  =》  *代表所有,一般生产环境会指定某一个请求网址 比如www.tm.com
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,DELETE,PUT");//OPTIONS一定要有
        response.setHeader("Access-Control-Allow-Headers","DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization");
        response.setContentType("application/json");//返回格式
        response.setCharacterEncoding("UTF-8");
//    response.setContentType("text/html;charset=UTF-8");

    /*
      跨域资源共享
        - 这是HTTP协议规定的安全策略
        - 配置资源共享的方式和目标方

        前端: node+vue -> admin.meetingfilm.com
        后端: springboot -> backend.meetingfilm.com

        -> 示例
        缺陷:如果出现跨域策略不足的情况,需要修改代码,重新部署
        -> Nginx
     */

        return null;
    }

}

jwt验证

package com.item.backed.utils.properties;

/**
 * jwt基础文件
 */
public class JwtProperties {

    private static com.item.backed.utils.properties.JwtProperties jwtProperties = new com.item.backed.utils.properties.JwtProperties();
    private JwtProperties(){}
    public static com.item.backed.utils.properties.JwtProperties getJwtProperties(){
        return jwtProperties;
    }

    public static final String JWT_PREFIX = "jwt";

    private String header = "Authorization";

    private String secret = "defaultSecret";

    private Long expiration = 604800L; // 默认是七天

    private String authPath = "login";

    private String md5Key = "randomKey";

    public static String getJwtPrefix() {
        return JWT_PREFIX;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public Long getExpiration() {
        return expiration;
    }

    public void setExpiration(Long expiration) {
        this.expiration = expiration;
    }

    public String getAuthPath() {
        return authPath;
    }

    public void setAuthPath(String authPath) {
        this.authPath = authPath;
    }

    public String getMd5Key() {
        return md5Key;
    }

    public void setMd5Key(String md5Key) {
        this.md5Key = md5Key;
    }
}

jwt ZuulFile文件

package com.item.backed.zuul.filters;

import com.alibaba.fastjson.JSONObject;
import com.item.backed.utils.common.vo.BaseResponseVO;
import com.item.backed.utils.properties.JwtProperties;
import com.item.backed.utils.util.JwtTokenUtilx;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @description :  jwt过滤
 **/
@Slf4j
@Component
public class JWTFilter extends ZuulFilter {
    /**
     * @Description: Filter类型
     * @Param: []
     * @return: java.lang.String
     * @Author: jiangzh
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * @Description: filter的执行顺序
     * @Param: []
     * @return: int
     * @Author: jiangzh
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * @Description: 是否要拦截
     * @Param: []
     * @return: boolean
     * @Author: jiangzh
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * @Description: Filter的具体业务逻辑
     * @Param: []
     * @return: java.lang.Object
     * @Author: jiangzh
     */
    @Override
    public Object run() throws ZuulException {
        // JWT工具类
        JwtTokenUtilx jwtTokenUtil = new JwtTokenUtilx();
        JwtProperties jwtProperties = JwtProperties.getJwtProperties();

        // ThreadLocal
        RequestContext ctx = RequestContext.getCurrentContext();
        // 获取当前请求和返回值
        HttpServletRequest request = ctx.getRequest();
        HttpServletResponse response = ctx.getResponse();

        // 提前设置请求继续,如果失败则修改此内容
        ctx.setSendZuulResponse(true);
        ctx.setResponseStatusCode(200);

        String pth = "/" + jwtProperties.getAuthPath();
        // 判断是否是登陆,如果是登陆则不验证JWT
        if (request.getServletPath().endsWith("/" + jwtProperties.getAuthPath())) {
            return null;
        }

        // 1、验证Token有效性 -> 用户是否登录过
        final String requestHeader = request.getHeader(jwtProperties.getHeader());
        String authToken = null;
        // Bearer header.payload.sign
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            authToken = requestHeader.substring(7);

            //验证token是否过期,包含了验证jwt是否正确
            try {
                boolean flag = jwtTokenUtil.isTokenExpired(authToken);
                if (flag) {
                    renderJson(ctx, response, BaseResponseVO.noLogin());
                } else {
                    // 2、解析出JWT中的payload -> userid - randomkey
                    String randomkey = jwtTokenUtil.getMd5KeyFromToken(authToken);
                    String userId = jwtTokenUtil.getUsernameFromToken(authToken);
                    // 3、是否需要验签,以及验签的算法

                    // 4、判断userid是否有效
                    // TODO
                }
            } catch (JwtException e) {
                //有异常就是token解析失败
                renderJson(ctx, response, BaseResponseVO.noLogin());
            }
        } else {
            //header没有带Bearer字段
            renderJson(ctx, response, BaseResponseVO.noLogin());
        }

        return null;
    }


    /**
     * 渲染json对象
     */
    public static void renderJson(RequestContext ctx, HttpServletResponse response, Object jsonObject) {
        // 设置终止请求
        response.setHeader("Content-Type", "application/json;charset=UTF-8");
        ctx.setSendZuulResponse(false);
        ctx.setResponseBody(JSONObject.toJSONString(jsonObject));
    }

}

前端请求

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值