微服务---gateway网关

发布于:2024-05-10 ⋅ 阅读:(24) ⋅ 点赞:(0)

目录

gateway作用

gateway使用

添加依赖

配置yml文件

自定义过滤器

nacos上的gateway的配置文件


我们现在知道了通过nacos注册服务,通过feign实现服务间接口的调用,那对于不同权限的用户访问同一个接口,我们怎么知道他是否具有访问的权限呢?或者我们怎么判断是否用户已经登录了呢?这些都可以通过gateway进行实现~

gateway作用

        Spring Cloud Gateway是Spring Cloud生态系统中的一员,它被设计用于处理所有微服务的入口流量。作为一个反向代理,它不仅提供了负载均衡和路由功能,还支持灵活的过滤器机制,过滤器可以在请求进入网关和离开网关时执行,用于处理各种逻辑,如身份验证、日志记录和性能监测,使得开发者能够定制和扩展其功能。

下图提供了一个关于 Spring Cloud Gateway 如何工作的高层次概述。

        客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被虚线分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。所有的 "pre" (前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,"post" (后)过滤器逻辑被运行。 

gateway使用

添加依赖

 <!--nacos服务注册发现依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--网关gateway依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--开启Spring Cloud 应用程序启动时加载bootstrap配置文件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.1.4</version>
        </dependency>

配置yml文件

gateway的配置文件其实就是将所有接口配置到web服务中,然后通过具体的过滤原则来访问每个接口

    其中- Authoriza=false表示是否需要通过token验证,这个为我们自定义的一个过滤原则名称的前缀,- StripPrefix=1为在发送请求时是否需要去掉第一层路径,比如/api/user/**如果StripPrefix=1表示api需要去掉,实际访问的是/user/**接口。

自定义过滤器

 AuthorizaGatewayFilterFactory使我们自定义的一个过滤器,gateway的自定义过滤器名字是有一定要求的,即 “你想取的过滤器名字前缀+GatewayFilterFactory”,这里以我的为例,然后把前缀作为刚刚配置文件的配置进行配置

然后我们实现这个自定义的过滤器

package com.yinan.authorize;

import com.alibaba.fastjson.JSONArray;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;


@Component
@Slf4j
@Data
@ConfigurationProperties(prefix ="exclude")
public class AuthorizaGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizaGatewayFilterFactory.Config> {

    /**
     * 需要放行的授权接口(nacos中的配置文件)
     */
    private String[] path;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 授权token
     */
    private static final String AUTHORIZE_TOKEN = "Authorization";

    /**
     *
     */
    private static final String AUTHORIZE_IP = "x-access-ip";

    /**
     * 用来标记是否需要授权校验
     */
    private static final String AUTHORIZE_RESOURCE = "x-access-resource";


    public AuthorizaGatewayFilterFactory() {
        super(Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("enabled");
    }


    @Override
    public GatewayFilter apply(Config config) {
        log.info("你已经进入gateway的过滤器中了----------");
        return (exchange, chain) -> {
            if (!config.enabled) {
                return chain.filter(exchange);
            }
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            HttpHeaders headers = request.getHeaders();
            ServerWebExchange build = null;

//            从header中获取token信息
            String token = headers.getFirst(AUTHORIZE_TOKEN);

//            获取用户IP
            String ip = headers.getFirst(AUTHORIZE_IP);
            log.info("当前访问url: " + request.getURI());
            if (token == null) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            log.info("请求的token为:" + token);
            log.info("请求的ip为:"+ ip);
            if (!"".equals(token)) {
                try {
                    ValueOperations<String, String> value = redisTemplate.opsForValue();
                    String userid = value.get(ip + "_user_" + "_userid_" + token);
                    System.out.println("userid:"+ userid);
                    String username = value.get(ip + "_user_" + "_username_" + token);
                    String auth=value.get(ip+"_token_"+userid);
                    if(auth==null){
                        response.setStatusCode(HttpStatus.UNAUTHORIZED);
                        return response.setComplete();
                    }
//            设置请求头
                    ServerHttpRequest host = exchange.getRequest().mutate().headers(httpHeaders -> {
                        httpHeaders.add("USER-ID", userid);
                        httpHeaders.add("USER-NAME", username);
                    }).build();
                    build = exchange.mutate().request(host).build();
//            判断是否需要接口授权校验
                    if (StringUtils.isNotBlank(headers.getFirst(AUTHORIZE_RESOURCE))) {
                        Boolean authority = getAuthority(request.getURI().toString(), userid);
                        if (authority) {
                            response.setStatusCode(HttpStatus.UNAUTHORIZED);
                            return response.setComplete();
                        }
                    }
//            黑名单校验
//                    if (checkBlackList(ip, username)) {
//                        response.setStatusCode(HttpStatus.FORBIDDEN);
//                        return response.setComplete();
//                    }
                } catch (Exception e) {
                    e.printStackTrace();
//                    token无效
                    log.error("出现异常【{}】", e.getMessage());
                    //设置状态码
                    response.setStatusCode(HttpStatus.UNAUTHORIZED);
                    return response.setComplete();
                }
            }
//认证通过,将用户ID进行传递到下游服务器
            return chain.filter(build);
        };
    }

    /**
     * 校验黑名单
     * @param ip
     * @param username
     * @return
     */
//    private boolean checkBlackList(String ip, String username) {
//
//    }

    /**
     *  获取用户权限(接口权限校验)
     * @param url
     * @param userid
     * @return
     */
    private Boolean getAuthority(String url, String userid) {
        //放行接口不需要做授权校验,直接放行
        for (String path:path
             ) {
            if(url.indexOf(path)!=-1){
                return false;
            }
        }
        //获取用户权限
        String resource=redisTemplate.opsForValue().get("userPermission:"+userid);
        List<String> list= JSONArray.parseArray(resource,String.class);
        for (String res:list
             ) {
            if(url.indexOf(res)!=-1){
                return false;
            }
        }
        return true;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        /**
         * 控制是否开启认证
         */
        private boolean enabled;
    }

}

如果你看了我之前讲的nacos配置的内容,那你应该就能理解以下截图中的意思,没有了解过nacos的话建议先去看看以下文章:微服务----nacos配置及简单使用

nacos上的gateway的配置文件

management:
  # 端点检查(健康检查)
  endpoints:
    web:
      exposure:
        include: "*"
spring:
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      # 处理跨域
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowedMethods: "*"
      discovery:
        locator:
          # false为服务器名不自动匹配,true则相反
          enabled: false
          # 服务名以小写进行匹配
          lowerCaseServiceId: true
      routes:
        # web服务
        - id: service-1
          uri: lb://data-server
          predicates:
            - Path=/scriptsContent/**
          filters:
          #开启token验证
            - Authoriza=true
            - StripPrefix=0
        
        - id: service-2
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - Authoriza=false
            - StripPrefix=0

#需要放行的授权接口
exclude:
  path:
    - /system/dictionary/by
    - /system/resource/by
    - /system/role/by
    - /system/api/user/by
    - /system/black/by
    - /oss
    - /web/article/by
    - /web/code/by

pattern:
  dateformat: yyyy/MM/dd hh:mm:ss

以上就是我的具体的过滤器实现逻辑,但是需要根据你实际的项目代码逻辑对这个过滤器进行修改 。

        实现后,当前端调用你的接口时,会经过gateway网关中的过滤器看看你是否有这个权限进行访问,或者是否需需要经过登录后才能访问,如果以上都没问题,就会放行到下游过滤器继续进行检查,如果都没有问题最后就会调用到你的接口~

        以上几篇博文我们大致讲了一下微服务的搭建和使用,本人也是最近刚学会微服务,对于简单的微服务搭建你可以根据我的这几篇博文进行学习,如果版本这些没什么问题是肯定能实现的,至于这些技术较深的理解和学习如果你有兴趣可以继续进行专研学习,我们共同进步~