Spring Cloud GatewayカスタムTokenチェックフィルタ

12808 ワード

すべてのビジネス開発はニーズに基づいています.まず、ニーズを見てみましょう.
アクセスゲートウェイの要求に対してtoken検証を行い、token検証が通過した場合にのみバックエンドサービスに転送され、そうでなければ直接401に戻る
本明細書で説明するコードの例は、シーンに適用されます.
tokenはredisに格納され、keyはユーザのuidである
依存pom.xml


    4.0.0

    com.winture
    api-gateway
    0.0.1-SNAPSHOT
    jar

    api-gateway
    Demo project for Spring Boot

    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.4.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
        Finchley.SR1
    

    
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        

        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

Spring Cloud Gatewayでは、主にGlobalFilterとGatewayFilterの2種類のフィルタがあります.
  • GlobalFilter:グローバルフィルタ、すべてのルーティングに対して
  • GatewayFilter:指定されたルーティングにのみ機能する
  • 1、カスタムGatewayFilter
    カスタムGatewayFilterには、GatewayFilterインタフェースを直接実装する方法と、AbstractGatewayFilterFactoryクラスを継承する方法の2つがあります.
    1.1 GatewayFilterインタフェースの実装
    package com.winture.gateway.filter;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.core.Ordered;
    import org.springframework.data.redis.core.StringRedisTemplate;
    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.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    /**
     * token     
     * @Version V1.0
     */
    public class AuthorizeGatewayFilter implements GatewayFilter, Ordered {
    
        private static final String AUTHORIZE_TOKEN = "token";
        private static final String AUTHORIZE_UID = "uid";
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Override
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            String uid = headers.getFirst(AUTHORIZE_UID);
            if (token == null) {
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }
            if (uid == null) {
                uid = request.getQueryParams().getFirst(AUTHORIZE_UID);
            }
    
            ServerHttpResponse response = exchange.getResponse();
            if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            String authToken = stringRedisTemplate.opsForValue().get(uid);
            if (authToken == null || !authToken.equals(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
    
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    
    }

    まずヘッダヘッダ情報からuidとtoken情報を取得し、tokenまたはuidがnullの場合、要求パラメータから再取得を試み、tokenまたはuidが依然として存在しない場合、直接401状態に戻り、同時に要求を終了する.両方が存在する場合、保存authTokenがuidに従ってredisから読み出され、要求で送信tokenと比較する、比較するようにフィルタチェーンを通過し続ける、そうでなければ直接要求を終了し、401に戻る.
    AuthorizeGatewayFilterをどのように適用しますか?
    package com.winture.gateway;
    
    import com.winture.gateway.filter.AuthorizeGatewayFilter;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.gateway.route.RouteLocator;
    import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
    import org.springframework.context.annotation.Bean;
    
    @SpringBootApplication
    public class GatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(GatewayApplication.class, args);
        }
    
        @Bean
        public RouteLocator routeLocator(RouteLocatorBuilder builder) {
            return builder.routes().route(r ->
                    r.path("/user/list")
                    .uri("http://localhost:8077/api/user/list")
                    .filters(new AuthorizeGatewayFilter())
                    .id("user-service"))
                .build();
        }
    }

    1.2 AbstractGatewayFilterFactoryクラスの継承
    package com.winture.gateway.filter.factory;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    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 java.util.Arrays;
    import java.util.List;
    
    @Component
    public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory {
    
        private static final Log logger = LogFactory.getLog(AuthorizeGatewayFilterFactory.class);
    
        private static final String AUTHORIZE_TOKEN = "token";
        private static final String AUTHORIZE_UID = "uid";
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        public AuthorizeGatewayFilterFactory() {
            super(Config.class);
            logger.info("Loaded GatewayFilterFactory [Authorize]");
        }
    
        @Override
        public List shortcutFieldOrder() {
            return Arrays.asList("enabled");
        }
    
        @Override
        public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
            return (exchange, chain) -> {
                if (!config.isEnabled()) {
                    return chain.filter(exchange);
                }
    
                ServerHttpRequest request = exchange.getRequest();
                HttpHeaders headers = request.getHeaders();
                String token = headers.getFirst(AUTHORIZE_TOKEN);
                String uid = headers.getFirst(AUTHORIZE_UID);
                if (token == null) {
                    token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
                }
                if (uid == null) {
                    uid = request.getQueryParams().getFirst(AUTHORIZE_UID);
                }
    
                ServerHttpResponse response = exchange.getResponse();
                if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) {
                    response.setStatusCode(HttpStatus.UNAUTHORIZED);
                    return response.setComplete();
                }
                String authToken = stringRedisTemplate.opsForValue().get(uid);
                if (authToken == null || !authToken.equals(token)) {
                    response.setStatusCode(HttpStatus.UNAUTHORIZED);
                    return response.setComplete();
                }
                return chain.filter(exchange);
            };
        }
    
        public static class Config {
            //         
            private boolean enabled;
    
            public Config() {}
    
            public boolean isEnabled() {
                return enabled;
            }
    
            public void setEnabled(boolean enabled) {
                this.enabled = enabled;
            }
        }
    }

    AuthorizeGatewayFilterFactoryをどのように適用しますか?
    #       
    spring:
      cloud:
        gateway:
          routes:
          - id: user-service
            uri: http://localhost:8077/api/user/list
            predicates:
            - Path=/user/list
            filters:
            #        ,  true     ,false    
            #        spring cloud gateway   GatewayFilterFactory  
            - Authorize=true

    上記の2つの方式はいずれもアクセスゲートウェイの特定の要求に対してtoken検証を実現することができ、すべての要求に対してtoken検証を行う場合はGlobalFilter方式を実現することができる.
    2、カスタムGlobalFilter
    package com.winture.gateway.filter;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.data.redis.core.StringRedisTemplate;
    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.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    /**
     * token       
     * @Version V1.0
     */
    @Component
    public class AuthorizeFilter implements GlobalFilter, Ordered {
        private static final String AUTHORIZE_TOKEN = "token";
        private static final String AUTHORIZE_UID = "uid";
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Override
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            String uid = headers.getFirst(AUTHORIZE_UID);
            if (token == null) {
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }
            if (uid == null) {
                uid = request.getQueryParams().getFirst(AUTHORIZE_UID);
            }
    
            ServerHttpResponse response = exchange.getResponse();
            if (StringUtils.isEmpty(token) || StringUtils.isEmpty(uid)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            String authToken = stringRedisTemplate.opsForValue().get(uid);
            if (authToken == null || !authToken.equals(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
    
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }

    AuthorizeFilterをどのように適用しますか?
    追加するだけ
    @Component注記、追加の構成を必要とせず、GlobalFilterインタフェースを実現し、すべてのルーティングに自動的に役立ちます.
    3、まとめ
    Spring Cloud Gatewayに触れたばかりなので、あまり詳しくないところもありますが、上記のサンプルコードは参考にしているだけなので、間違ったところがあれば、指摘してください.
    コメント:
  • 上記のコードを実行するには、redisサービスを開始する必要があります.redisのアドレスとポートが構成されていないため、デフォルトではlocalhostと6379ポートが採用されています.一致しない場合は、自分でアプリケーションを使用してください.ymlファイルで構成すればいいです.
  • ゲートウェイのポートはデフォルトの8080を採用しています.
  • ゲートウェイを介して/user/listにアクセスするとtoken検証が通過すると、http://localhost:8077/api/user/list上、これは別のインタフェースサービスで、自分で実際の状況に応じて修正します.

  • 参考学習:
  • Spring Cloud Gateway公式ドキュメント
  • Spring Cloud Gatewayソースコード