Gateway Spring Security 공부 및 고민 내용 - nhnacademy-be10-WannaB/wannab-wiki GitHub Wiki

0. Front에서 login

@RequiredArgsConstructor
@Controller
public class AuthController {

    private final AuthService authService;

    @PostMapping("/asd")
    public ResponseEntity<Void> login() {
        LoginRequest loginRequest = new LoginRequest("dang", "aaaa1111@");
        LoginResponse response = authService.login(loginRequest);
        
        
        ResponseCookie accessCookie = CookieUtils.createCookie("access_token", response.accessToken(), Duration.ofMinutes(30), false);
        ResponseCookie refreshCookie = CookieUtils.createCookie("refresh_token", response.refreshToken(), Duration.ofDays(7), true);

        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, accessCookie.toString())
                .header(HttpHeaders.SET_COOKIE, refreshCookie.toString())
                .build();
    }
}

/asd 경로로 요청이오면 ID : dang, PW : aaaa1111@ 로 로그인 요청을 보낸다.

  • 요청을 보내는 방법은 authService.login 메서드를 사용함
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {

    private final AuthClient authClient;

    @Override
    public LoginResponse login(LoginRequest request) {
        ResponseEntity response = authClient.login(request);
        LoginResponse tokenDto = (LoginResponse) response.getBody();

        return tokenDto;
    }
}

AuthClient(FeignClient)를 활용해서 요청을 보냄

@FeignClient(name = "gateway", url = "localhost:8081")
public interface AuthClient {
    @PostMapping("/user-service/api/auth/login")
    ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request);

}
  • 이 요청시에는 당연히 accesstoken, refreshtoken이 없음
  • 일단 Gateway에서 로그가 찍히는 걸 보니, Gateway의 필터까지는 무조건 옴

1. Gateway에서

  • 당연히 글로벌필터로 설정했으니 요청이 들어옴
  • login 요청은 허용해줘야할듯?

근데 webflux를 진짜 쓰는거같은데..

  1. 일단 Cloud Document를 보면

[Spring Cloud Train Reference Documentation :: Spring Cloud Release](https://docs.spring.io/spring-cloud-release/reference/2023.0/index.html)

[Spring Cloud Gateway :: Spring Cloud Gateway](https://docs.spring.io/spring-cloud-gateway/reference/4.1/)

  1. 이부분은 나중에 추가 고민해보고

WhiteList 방식으로?

로그인과 회원가입 시에는 jwt가 없기 때문에 예외 처리를 해줘야 한다.

RouteValidator

@Component
public class RouteValidator {

    public static final List<String> openApiEndpoints = List.of(
            "/eureka",
            "/coupon/api/v1/coupon/issued-check/",
            "/auth/api/register",
            "/auth/api/login"
    );

    public Predicate<ServerHttpRequest> isSecured =
            request -> openApiEndpoints
                    .stream()
                    .noneMatch(uri -> request.getURI().getPath().contains(uri));

}
  • openApiEndpoints에 지정하고 싶은 경로를 설정해주면 되는데
  • 예를 들어 쿠폰 api의 모든 요청을 열어두고 싶으면
  • /coupon/api/**이 아닌
  • /coupon/api/ 이렇게 해줘야 한다.

글로벌 필터에서는

AuthenticationFilter

// Global Filter 적용
@Component
@Slf4j
public class AuthenticationFilter extends AbstractGatewayFilterFactory<AuthenticationFilter.Config> {

    @Autowired
    private RouteValidator validator;

    @Autowired
    private JwtUtil jwtUtil;

    public AuthenticationFilter() {
        super(Config.class);
    }

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

            if (validator.isSecured.test(request)) {
            	// JWT 검증 로직
                List<String> authHeaders = request.getHeaders().get(HttpHeaders.AUTHORIZATION);

                if (authHeaders == null || authHeaders.isEmpty()) {
                    throw new MissingAuthorizationHeaderException();
                }

                String authHeader = authHeaders.get(0);

                if (authHeader.startsWith("Bearer ")) {
                    String token = authHeader.substring(7);

                    try {
                        jwtUtil.validateToken(token);
                    } catch (Exception e) {
                        throw new UnauthorizedAccessException();
                    }
                } else {
                    throw new InvalidAuthorizationHeaderFormatException();
                }
            }

            // post
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                ServerHttpResponse response = exchange.getResponse();
                log.info("Custom Post filter: response code: " + response.getStatusCode());
            }));
        };
    }

    @Data
    public static class Config {
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
    }
}

흠…

[[SpringBoot] Spring Cloud Gateway Authentication 문제](https://gose-kose.tistory.com/27)

  • 이거 참고한건데 공부하다보니 깨달은건 결국..

Spring Security + Spring Cloud Gateway를 쓰면, WebFlux가 따라옴

  • 그래서.. 선택을 해야함
  1. Spring Security + Spring Cloud Gateway + WebFlux 를 하던지
  2. Spring Cloud Gateway + JWT 의존성만으로 해결하던지
  • 이거좋당

[토큰 인증과 권한체크를 Spring Cloud Gateway로 분리하기](https://yooyouny.tistory.com/38)

[인증? 인가? JWT 하나면 된다. (MSA 보안 쉽게 이해하기)](https://www.youtube.com/watch?v=KojezV-vDHQ&ab_channel=masiljangajji)

[Gateway, Spring Security, JWT 사용하여 인증 및 인가 처리하기](https://lincoding.tistory.com/120)

만약에 Spring Security + Gateway (WebFlux) 를 하면?

  • Spring Security를 Reactive(비동기)방식으로 공부해야함
    • 러닝 커브 높음
  • Mono / Flux, WebFilter 등의 개념을 이해해야함
  • 간단한 설정만으로도 검증로직을 구현할 수 있음
    • 확장성은 높을듯

Spring Cloud Gateway + JWT 필터(Custom Global Filter)로 하면?

⚠️ **GitHub.com Fallback** ⚠️