๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ’Š Java & Kotlin & Spring/- spring framework +

[Spring Cloud Gateway] Custom Filter ๋กœ ๊ฐ„๋‹จํ•œ Authentiction ํ•„ํ„ฐ ๋งŒ๋“ค๊ณ  ์ธ์ฆ ์ฒ˜๋ฆฌํ•˜๊ธฐ

by Wonit 2021. 4. 25.

ํ•ด๋‹น ๊ธ€์€ Spring Cloud Gateway ์˜ Built-in Route๋กœ Predicates์™€ Filter ์กฐ์ž‘ํ•˜๊ธฐ) ์— ์˜์กดํ•˜๋Š” ๊ธ€์ž…๋‹ˆ๋‹ค. ์‹ค์Šต ํ™˜๊ฒฝ์„ ๋”ฐ๋ผํ•˜์‹œ๋ ค๋ฉด ์ด์ „ ๊ธ€์„ ํ™•์ธํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

ํ˜„์žฌ ๊ธ€์—์„œ๋Š” ๋ชจ๋“  ์ธ์ฆ ๊ณผ์ • (jwt ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  payload ์— ๊ฐ’์„ ๋„ฃ๋Š” ์‹ค์ œ ๊ตฌํ˜„)์€ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์˜ ๋ชฉ์ ์€ Gateway ์—์„œ Custom Filter ๋งŒ๋“ค์–ด์„œ ํ† ํฐ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ „์ฒด์ ์ธ ํฐ ๊ทธ๋ฆผ๋งŒ ๋ณด์—ฌ์ฃผ๋ ค ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ์ธ์ฆ์„ ๊ตฌํ˜„ํ•˜๋ ค ํ•œ๋‹ค๋ฉด ์ฐธ๊ณ ๋งŒ ํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋ชฉ์ฐจ

  • Custom Filter
  • AbstractGatewayFilterFactory ์ƒ์†๊ณผ apply ์žฌ์ •์˜
  • ๊ฒ€์ฆํ•˜๊ธฐ

Custom Filter

์ง€๋‚œ ์‹œ๊ฐ„๊นŒ์ง€ ์šฐ๋ฆฌ๋Š” Spring Cloud Gateway๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” Built-in Predicates์™€ Filters๋ฅผ ์•Œ์•„๋ณด์•˜๋‹ค.

 

์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š” Custom Filter๋ฅผ ๋งŒ๋“ค๊ณ  Gateway Filters์— ๋“ฑ๋กํ•ด๋ณด์ž.

 

๋ฌด์—‡์„ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€?

๊ฐ„๋‹จํ•˜๊ฒŒ ๋ง ํ•˜๋ฉด JWT ํ† ํฐ ํŒŒ์‹ฑ์„ ํ•˜๋Š” Filter๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ด๋‹ค.

 

Microservice ์—์„œ ์ธ์ฆ์€ Stateless ํ•œ ๋ฐฉ์‹์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€ํ”ผํ•˜๋‹ค.


๋งŒ์•ฝ ๊ทธ๋ ‡์ง€ ์•Š์€ Session-Cookie ๊ธฐ๋ฐ˜ ์ธ์ฆ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ๋ชจ๋“  ์„œ๋ฒ„๊ฐ€ ํ•˜๋‚˜์˜ DB๋ฅผ ๋ฐ”๋ผ๋ณด๊ณ  ์‚ฌ์šฉ์ž์˜ Session๊ณผ Cookie๋ฅผ ๊ด€๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋ชจ๋“  ์„œ๋ฒ„์—์„œ Session์„ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

 

ํ•˜์ง€๋งŒ Request Header์— Token String์„ ๋‹ด์•„ statelessํ•œ ์ธ์ฆ์„ ์‚ฌ์šฉํ•˜๋ฉด ์œ„์˜ ๋ฌธ์ œ๋ฅผ ์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๊ทธ๋ ‡๊ธฐ์— ์šฐ๋ฆฌ๋Š” ํ† ํฐ์„ ์ด์šฉํ•œ ์ธ์ฆ ๋ฐฉ์‹์„ ์œ„ํ•ด Gateway๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

์™œ ์ธ์ฆ์„ Gateway์—์„œ ํ• ๊นŒ?

 

๊ทธ ์ด์œ ๋Š” ๋ฐ”๋กœ Gateway์˜ ํ๋ฆ„์„ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

์œ„์—์„œ ๋ณธ๋‹ค๋ฉด ๋‹น์—ฐํ•˜๊ฒŒ ๋ชจ๋“  ์š”์ฒญ์ด Gateway๋กœ ๋ชฐ๋ฆฌ๊ฒŒ ๋œ๋‹ค.

 

๊ทธ๋Ÿผ ๊ฐ๊ฐ์˜ ์„œ๋ฒ„์—์„œ ์ธ์ฆ์„ ํ•  ํ•„์š” ์—†์ด Gateway ์—์„œ ํ•œ ๋ฒˆ๋งŒ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ๋‹ค๋ฅธ ์„œ๋ฒ„๋“ค์€ ์š”์ฒญ์ด ์˜ค๋ฉด ์ธ์ฆ๋œ ์ˆœ์ˆ˜ ์š”์ฒญ์ด๋ผ๊ณ  ํ•˜๋ฉด ์–ด๋–จ๊นŒ?


๋ฌผ๋ก  ์–ด๋– ํ•œ ์ƒํ™ฉ์—์„œ๋„ ๋ชจ๋“  ๊ณต๊ฒฉ์„ ๋ง‰์„ ์ˆ˜๋Š” ์—†๊ฒ ์ง€๋งŒ ๋‹น์žฅ์œผ๋กœ์„œ๋Š” ํ•ด๋‹น ๋ฐฉ๋ฒ•์ด ์ข‹์€๊ฒƒ ๊ฐ™๊ณ  ์ถฉ๋ถ„ํžˆ ์ด๋ฅผ ๊ตฌํ˜„ํ• ๋งŒ ํ•˜๋‹ค.

 

๊ทธ๋Ÿผ ์ง€๊ธˆ๋ถ€ํ„ฐ ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค๊ณ  ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ณผ์ •์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

 

์•ž์„œ ๋งํ–ˆ์ง€๋งŒ ํ˜„์žฌ ๊ธ€์—์„œ๋Š” ๋ชจ๋“  ์ธ์ฆ ๊ณผ์ •, jwt ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  payload ์— ๊ฐ’์„ ๋„ฃ๋Š” ์‹ค์ œ ๊ตฌํ˜„์€ ํ•˜์ง€ ์•Š์œผ๋ ค ํ•œ๋‹ค. ์ด๋ฒˆ ๊ธ€์˜ ๋ชฉ์ ์€ ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ† ํฐ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ „์ฒด์ ์ธ ํฐ ๊ทธ๋ฆผ๋งŒ ๋ณด์—ฌ์ฃผ๋ ค ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ์ธ์ฆ์„ ๊ตฌํ˜„ํ•˜๋ ค ํ•œ๋‹ค๋ฉด ์ฐธ๊ณ ๋งŒ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค.

 

CustomAuthFilter๋กœ ์ธ์ฆ ๊ตฌํ˜„ํ•˜๊ธฐ

Spring Cloud Gateway๋Š” Zuul ์— ๋น„๋™๊ธฐ ํ†ต์‹  ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋œ ๋ฒ„์ „์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์‰ฝ๋‹ค.


๊ทธ๋ž˜์„œ Blocking ๊ตฌ์กฐ๊ฐ€ ์•„๋‹Œ Non-Blocking ๊ตฌ์กฐ์ธ Spring WebFlux ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Filter๋“ค์„ ๋ชจ๋‘ Flux๋กœ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

 

ํ† ํฐ์„ ๊ฒ€์ฆํ•˜๋Š” ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜๋Ÿฌ๊ฐˆ ๊ฒƒ์ด๋‹ค.

 

  1. ์‚ฌ์šฉ์ž ์š”์ฒญ
  2. Handler Mapping ์ด Predicates ๊ฒ€์‚ฌ
  3. Pre Filter ์—์„œ Request Header ์— ์žˆ๋Š” token ํŒŒ์‹ฑ
  4. ๋งŒ์•ฝ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด 401 Error Reject
  5. ์กด์žฌํ•œ๋‹ค๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ ์„œ๋ฒ„๋กœ ์š”์ฒญ ์ „๋‹ฌ

 

CustomFilter.class ์ƒ์„ฑํ•˜๊ธฐ

์šฐ์„  ์‚ฌ์šฉ์ž ์ •์˜ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” Spring Cloud Gateway ๊ฐ€ ์ถ”์ƒํ™”ํ•ด ๋†“์€ AbstractGatewayFilterFactory ๋ฅผ ์ƒ์†๋ฐ›๋Š”๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  GatewayFilterFactory ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์šฐ๋ฆฌ์˜ ๋กœ์ง์„ ๋„ฃ์œผ๋ฉด ๋˜๋Š”๋ฐ, ํ•ด๋‹น ์ถ”์ƒํ™” ๋ฉ”์„œ๋“œ๋ฅผ GatewayFilter apply(Config config) override ํ•œ๋‹ค.

 

@Component
public class CustomAuthFilter extends AbstractGatewayFilterFactory<CustomAuthFilter.Config> {
    public CustomAuthFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return null;
    }

    public static class Config {

    }
}

 

Config class ์—์„œ๋Š” Configuration ์†์„ฑ๋“ค์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋˜๋Š”๋ฐ, ๋‹ค์Œ์— ์žˆ์„ Global Filter์—์„œ ์ž์„ธํ•˜๊ฒŒ ์ด์•ผ๊ธฐํ•ด๋ณด๋ ค ํ•œ๋‹ค.
ํ˜„์žฌ๋Š” ๊ทธ๋ƒฅ ๋น„์–ด์žˆ๋Š” ํด๋ž˜์Šค๋กœ ๋„ฃ์–ด์ฃผ์ž.

 

ํ† ํฐ ๊ฒ€์ฆ ๋กœ์ง ์ถ”๊ฐ€ํ•˜๊ธฐ

์ด์ œ ํ† ํฐ ๊ฒ€์ฆ์„ ํ•  ๋กœ์ง์„ apply ๋ฉ”์„œ๋“œ์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

 

@Component
public class CustomAuthFilter extends AbstractGatewayFilterFactory<CustomAuthFilter.Config> {
    public CustomAuthFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {

        });
    }

    public static class Config {

    }
}

 

apply ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ServerWebExchange ํ˜•ํƒœ๊ณ  ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ GatewayFilterChain ๋žŒ๋‹ค ํ•จ์ˆ˜์ด๋‹ค.

 

ServerHttpRequest request = exchange.getRequest(); // Pre Filter

ServerHttpResponse response = exchange.getResponse(); // Post Filter

 

exchange ์˜ Request๋ฅผ ๋ฐ›์•„์˜ค๋ฉด Pre Filter๋กœ ์ ์šฉ๋˜๊ณ  Post Filter๋Š” Response๋กœ ๋ฐ›์•„์˜ค๋ฉฐ ๋œ๋‹ค.

 

์ฃผ์˜ํ•ด์•ผํ•  ์ ์ด ServletReqeust, Response๊ฐ€ ์•„๋‹Œ Spring Reactive ์˜ Response, Requset์—ฌ์•ผ ํ•œ๋‹ค.

 

ํ† ํฐ ๊ฒ€์ฆ ๋กœ์ง ์™„์„ฑํ•˜๊ธฐ

@Component
public class CustomAuthFilter extends AbstractGatewayFilterFactory<CustomAuthFilter.Config> {
    public CustomAuthFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // Request Header ์— token ์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๋•Œ
            if(!request.getHeaders().containsKey("token")){
                return handleUnAuthorized(exchange); // 401 Error
            }

            // Request Header ์—์„œ token ๋ฌธ์ž์—ด ๋ฐ›์•„์˜ค๊ธฐ
            List<String> token = request.getHeaders().get("token");
            String tokenString = Objects.requireNonNull(token).get(0);

            // ํ† ํฐ ๊ฒ€์ฆ
            if(!tokenString.equals("A.B.C")) {
                return handleUnAuthorized(exchange); // ํ† ํฐ์ด ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๋•Œ
            }

            return chain.filter(exchange); // ํ† ํฐ์ด ์ผ์น˜ํ•  ๋•Œ

        });
    }

    private Mono<Void> handleUnAuthorized(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();

        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return response.setComplete();
    }

    public static class Config {

    }
}

application.yml์— ์šฐ๋ฆฌ๊ฐ€ ์ •์˜ํ•œ CustomFilter ๋“ฑ๋กํ•˜๊ธฐ

server:
  port: 8000

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE
          predicates:
            - Path=/user/**
          filters:
            - RewritePath=/user/?(?<segment>.*), /$\{segment}
            - CustomAuthFilter

        - id: order-service
          uri: lb://ORDER-SERVICE
          predicates:
            - Path=/order/**
          filters:
            - RewritePath=/user/?(?<segment>.*), /$\{segment}

 

user-serivce ์— CustomAuthFilter ๋ฅผ ์ถ”๊ฐ€์‹œ์ผœ์ฃผ๋ฉด ์šฐ๋ฆฌ์˜ ํ•„ํ„ฐ๊ฐ€ ๋“œ๋””์–ด Gateway ์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๊ฒ€์ฆํ•˜๊ธฐ

์šฐ๋ฆฌ์˜ ํ•„ํ„ฐ๊ฐ€ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ธฐ์กด์— ๊ตฌ์„ฑํ•˜๋˜ Service Mesh์™€ Microservices ๋ฅผ ์‹คํ–‰์‹œ์ผœ๋ณด์ž.

  • Eureka Server
  • Gateway Service
  • User Service
  • Order Service

๊ทธ๋ฆฌ๊ณ  Gateway๋กœ user service์— GET /info ์š”์ฒญ์„ ๋ณด๋‚ด๋ณด์ž.

 

ํ† ํฐ ์—†์ด ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

 

ํ˜„์žฌ ์š”์ฒญ์—๋Š” Request Header ์— token ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— 401 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.

 

invalid ํ•œ ํ† ํฐ์„ ํฌํ•จํ•œ ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

 

ํ† ํฐ ๊ฒ€์ฆ ๋กœ์ง์—์„œ A.B.C ๋ผ๋Š” ํ† ํฐ์ด ์™€์•ผ์ง€๋งŒ ์ •์ƒ ์š”์ฒญ์„ ๋ณด๋‚ด๊ฒŒ ํ•˜๋Š”๋ฐ, ์ž˜๋ชป๋œ ํ† ํฐ์„ ๋ณด๋‚ด๋ณด์ž.

 

 

์—ญ์‹œ 401 ์—๋Ÿฌ๋กœ ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋œ๋‹ค.

 

valid ํ•œ ํ† ํฐ์„ ํฌํ•จํ•œ ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

์ด๋ฒˆ์—๋Š” Valid ํ•œ ํ† ํฐ, A.B.C ๊ฐ’์„ ํ—ค๋”์— ํฌํ•จ์‹œ์ผœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ณด์ž.

 

๊ทธ๋Ÿผ ์œ„์™€๊ฐ™์ด 200 OK๋กœ ์ •์ƒ์ ์œผ๋กœ ์šฐ๋ฆฌ์˜ ์š”์ฒญ์ด ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ ๋œ User-Service๋กœ ๋„์ฐฉํ•˜๊ฒŒ ๋œ๋‹ค.

 


์˜ค๋Š˜์€ ์ด๋ ‡๊ฒŒ Spring Cloug Gateway๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ„๋‹จํ•œ ์ธ์ฆ ํ•„ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

 

์ด๋Ÿฐ ์ธ์ฆ์€ ๊ทธ ์–ด๋–ค ์„œ๋น„์Šค์—์„œ๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ์ง€๋งŒ ์ธ์ฆ์„ ์œ„ํ•œ ํ† ํฐ ๊ฒ€์ฆ ํ๋ฆ„์€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.

 

ํ•ด๋‹น ๊ธ€์„ ๋ณด๊ณ  ํ๋ฆ„์„ ํŒŒ์•…ํ•œ ๋’ค ๊ฐ์ž์˜ ํ”„๋กœ์ ํŠธ์— ์„ฑ๊ณต์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋Œ€ํ•œ๋‹ค.

 

๋Œ“๊ธ€