OpenAPI Generator 클라에서 JWT 토큰 전달하기

이상민·2023년 3월 21일
0
post-thumbnail

한줄 요약

  • API 서버 같은 멀티 스레드 환경에서 OpenAPI Generator로 생성한 구현 코드에 사용자 토큰을 릴레이하고 싶다면 ApiClient를 요청마다 생성해야한다

문제 상황

  • OpenAPI Specification 3는 HTTP API를 정의 하기 위한 명세이다. yaml 파일과 같은 형태로 작성하기 때문에 특정 언어에 종속되지 않는 API 스펙을 작성할 수 있다
  • OpenAPI Generator는 이런 OAS로 작성된 문서로부터 API를 사용하는 클라이언트/서버 코드를 다양한 언어로 생성하기 위한 프로젝트이다
  • 운영 중인 서버 하나에서 API를 관리하기 위해 OAS를 작성하고는 있었으나, 이를 통해 코드 생성은 하고 있지 않았다
  • 이 서버에서는 불편한 점이 없었지만, 서버를 호출하는 Webflux API 게이트 웨이에서 매번 DTO를 또 만들어주고 하려니 여간 귀찮은게 아니었다
  • 다행이도 OpenAPI Generatorrk Java WebClient 클라이언트 코드 생성을 지원하기 때문에 사용해보기로 했다.
  • 대충 설정해서 코드 생성을 하면 아래와 같은 클라이언트가 생성된다
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2023-03-21T20:39:48.722754+09:00[Asia/Seoul]")
public class OrderApi {
    private ApiClient apiClient;

    public OrderApi() {
        this(new ApiClient());
    }

    @Autowired
    public OrderApi(ApiClient apiClient) {
        this.apiClient = apiClient;
    }

    public ApiClient getApiClient() {
        return apiClient;
    }

    public void setApiClient(ApiClient apiClient) {
        this.apiClient = apiClient;
    }

    /**
     * 주석
     */
    public Flux<Response> apiName(Request req) throws WebClientResponseException {
        // 구현
    }
}
  • 이때 apiName() 같은 메소드들 하나하나가 api에 대응된다
  • 인증/인가를 위해 사용자의 jwt 토큰이 필요한 경우에 이를 어떻게 전달해야하는지 난감했다

인증/인가

  • 약간의 뻘짓을 하다가 찾아보니 클라 구현체에서 api를 호출하기 위해 사용되는 ApiClient 객체에 토큰을 설정 해야한다는 것을 찾아냈다
  • ApiClient는 2019년도부터 bearer 토큰을 아래 이슈에서 볼 수 있듯 지원한다
public class ApiClient extends JavaTimeFormatter {
	...
	private Map<String, Authentication> authentications;
	...
	public void setBearerToken(String bearerToken) {
        for (Authentication auth : authentications.values()) {
            if (auth instanceof HttpBearerAuth) {
                ((HttpBearerAuth) auth).setBearerToken(bearerToken);
                return;
            }
        }
        throw new RuntimeException("No Bearer authentication configured!");
    }
  	...
  }
  • 간단하게 ApiClient를 빈으로 등록해서 사용하면 되나?라고 했다가… authentications가 쓰레드 세이프하지 않아 아차!했다. 여러 요청이 동시에 들어오는 웹서버, 더군다나 이벤트 루프 모델인 Webflux에서 어떻게 처리해야할지 고민이 됐다.

멀티 쓰레드 환경에서 OpenAPI Generator 클라이언트

  • OpenAPI Generator로 생성된 Api 클래스들은 다음과 같은 ApiClient 객체 설정 방법을 제공한다
   public OrderApi() {
        this(new ApiClient());
    }

    @Autowired
    public OrderApi(ApiClient apiClient) {
        this.apiClient = apiClient;
    }

    public ApiClient getApiClient() {
        return apiClient;
    }

    public void setApiClient(ApiClient apiClient) {
        this.apiClient = apiClient;
    }
  • 뭔가 멀티 쓰레드 환경에서는 계속 ApiClient를 새로 생성하기를 원하는거 같은데… 확신이 서지 않았다. 그리고 ApiClient를 매번 생성했을때 성능 문제가 우려되었다
  • 다행이도 ApiClient 객체의 생성에서 가장 무거운 동작은 WebClient 객체를 생성하는 것이고, ApiClient는 WebClient 객체를 주입 받는 생성자가 있다. WebClient 생성 이외에 생성자에서 하는 나머지 작업은 빈 해시맵 만들기 같은 가벼운 연산이었다.
    public ApiClient(WebClient webClient, ObjectMapper mapper, DateFormat format) {
        this(Optional.ofNullable(webClient).orElseGet(() ->buildWebClient(mapper.copy())), format);
    }

    private ApiClient(WebClient webClient, DateFormat format) {
        this.webClient = webClient;
        this.dateFormat = format;
        this.init();
    }

    protected void init() {
        // Setup authentications (key: authentication name, value: authentication).
        authentications = new HashMap<String, Authentication>();
        authentications.put("bearer", new HttpBearerAuth("bearer"));
        // Prevent the authentications from being modified.
        authentications = Collections.unmodifiableMap(authentications);
    }
  • 이제 ApiClient를 매번 만드는게 Best Practice라는 확신만 있으면 되는데... 이게 생각보다 찾기 힘들었다. 인터넷을 한참 뒤지다가… 결국 ApiClient를 매번 생성하는 것이 추천하는 방법이라는 문구를 OpenAPI Generator 레포의 예시 readme 저 아래 구석에서 찾았다 ㅠㅠㅠ
  • 서블릿 환경이었다면 ApiClient를 요청 스코프 빈으로 등록한다던가 해서 직접 객체화하는 코드 없이 처리할 수 있었겠으나, 리액티브 스택에서는 그게 어렵기 때문에 약간 고민이 되었다. 정 하려면 Reactor Context를 사용해서 뭔가 해볼 수도 있을거 같긴한데, 괜히 복잡해지고 할거 같아서 그냥 생성자 호출해서 하기로 했다
profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

0개의 댓글