Socket 인증 with API Gateway + Refresh JWT

Sieun Sim·2020년 5월 31일
14

서버개발캠프

목록 보기
20/21

WebSocket Handshake Request에 header 달기

http→websocket upgrade를 위한 handshake http 요청에는 custom header를 달 수 없다. Authorization header를 달 수가 없다는 것이다. 특히 API Gateway를 사용하는 경우에 문제가 된다. 처음에는 upgrade request도 HTTP 프로토콜로 GET으로 보내니까 upgrade http request에 Authorization header를 붙여 gateway에서 다른 HTTP request들과 마찬가지로 JWT를 파싱하려고 했다. 그런데 이 upgrade request를 STOMP client에서 따로 어떻게 조정할 수가 없다는 것이다... 이걸 어떻게든 무수한 삽질을 했는데 결국은 오피셜하게 불가능한 일이었다. 보안상의 이유 + 브라우저의 부담 인 것 같은데 이에 대한 논쟁을 찾았다.

Header 달게 해줘라 vs 싫다 논쟁

Websocket - support for custom headers for handshake · Issue #3062 · whatwg/html

위의 깃헙 이슈에는 Authorization header를 달 수 있게 해 달라는 주장vs 본인이 크롬 웹소켓 개발에 기여했다는 어떤 사람의 쿼리스트링으로 날려도 괜찮다는 주장이 부딪히고 있다. 쿼리스트링으로 JWT를 확인한다는 것은 이메일 인증에서 주로 사용하듯이 요청 자체의 URL에 토큰을 함께 보내 서버의 컨트롤러 차원에서 토큰의 유효성을 검사한다는 것이다. 크롬 웹소켓 개발에 기여했다는 사람은 소켓의 path를 클라이언트 단에서 절대 접근할 수 없기 때문에 쿼리 스트링으로 토큰을 검사해도 충분하다고 했다. 또한 일단 connection을 establish 한 다음에 message 차원으로 직접 token을 보내 검사하는 방법도 괜찮을 거라고 했다. 반대파는 connection 자체에 대한 제한이 없으면 자원의 낭비일 거라고 주장하고 있고 나도 전적으로 동의한다. 그리고 새로 안 사실은, url path로 token을 넘길 경우 서버 로그에 jwt가 path로서 그대로 남아버린다는 것이다. 크롬 웹소켓 개발에 기여했다는 이 개발자는 서버 로그가 털렸다는건 다른것도 다 털렸다는거지 그건 굳이 토큰이 아니여도 이미 망한거야~ 이런 식이고 다른사람들은 무책임하다며 까고있다 ㅋㅋㅋㅋ..

header를 달 수 있게 해달라는 사람의 주장은, 결국 토큰 인증을 하려면 개발자들이 각자 socket connection 뒤에 알아서 작은 인증 시스템을 만들어 사용해야 하는데 이미 authorization header, cookie기반 토큰인증 등 정형화된 형식이 많이 있는데 왜 또 바퀴를 다시 만들어야만 하냐는 것이다. 내 생각이랑 완전 똑같아서 많이 공감이 갔다. header를 달기 싫어하는 쪽은 CORS preflight을 이용해 origin을 한 번 더 체크하는 것(웹소켓에 해더를 달려면 필요한 과정)에 대한 브라우저의 비용이 connection 완료 후 메시지로 한 번 더 체크하는 것보다 크다는 입장이다. 아주 흥미로운데..... 당장 구현하다가 직접 이 문제에 부딪힌 사람으로서 그냥 붙여주면 좋겠다 ㅠㅠㅠㅠ

CORS preflight이란?

preflight 요청은 브라우저가 필요하다면 알아서 해주는 요청이다. Access-Control-Request-Method, Access-Control-Request-Headers를 서버에 보내 실제 request를 날려도 될 지 물어본다. 예를 들어,

요청

Origin: http://yourdomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header

응답

Access-Control-Allow-Origin: http://yourdomain.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-Custom-Header

preflight의 요청과 응답이 이렇다고 해보자.

특히 응답의 Access-Control-Allow-Headers 는 요청의 Access-Control-Request-Headers와 완전히 같아야 한다. 모든 것을 허용한다는 뜻인 '*' 도 용납되지 않는다. 응답의 Access-Control-Allow-Methods는 요청의 Access-Control-Request-Method를 포함해야 할 것이다. 이 두가지가 통과되면 그때서야 진짜 request를 날린다.

위 개발자의 말은 한마디로 socket에 헤더를 달면서 이런 과정을 추가하기가 빡세다는 것이다.

그래서 무엇이 가장 바람직하냐?

secondary socket token 추가하기

원래 쓰던 유저인증 jwt로 인증을 먼저 한 뒤에 추가적인 30초 안팎의 짧은 토큰을 만들어 query param으로서 인증한다. 이 방법으로 모든 문제가 해결된다.

  1. access token이 만료되어 refresh가 필요하거나 refresh token도 유효하지 않아서 로그인을 다시 해야하는 과정이 있다면 STOMP CONNECT 과정에서 복잡하게 처리하는 것이 아니라, 아예 socket connection에 진입하기 전에 login 인증을 완료할 수 있다.
  2. 위의 과정을 위해 별도의 end point를 만들 필요 없이 API Gateway에서 쓰던대로 그대로 쓸 수 있다. Spring Cloud Gateway에서 ws 프로토콜도 routing을 지원해주기 때문에 Gateway를 포기할 필요 없이 login refresh-JWT 방식을 그대로 사용할 수 있다.
  3. query param으로 인증하게 되는 토큰은 로그인 토큰과는 다르고, 로그인한 유저에게만 발급될 뿐 아니라 빠르게 만료되므로 서버 로그에서 노출되더라도 안전하다.

login jwt를 인증한 후에 소켓을 열 일이 있다면 완전히 또다른 두번째 토큰을 유저에게 발급하고 저장해둔다. 이 토큰은 query param으로 들어갈 것이므로 더 짧고 빨리 만료되어야 한다. STOMP를 사용하면 url path를 통해 message를 보낼텐데, 이 url path에 이 짧은 토큰을 넣는 것이다. 사실 client 단에서는 유저의 별다른 activity 없이 [ login jwt 인증받기-socket 요청하기- url path의 socket token 인증받기] 이 과정이 알아서 처리되어야 하므로 socket token은 시간이 아주 짧아도 된다. 첫 연결할 때만 인증을 받으면 되는 것이다. 30초 안팎으로(30초까지도 필요 없을 듯) 만들어서 확인한다. 스택오버플로우피셜이지만 슬랙에서 이 방법을 사용하며 슬랙은 소켓 토큰을 30초를 준다고 한다.

카카오 뉴스 댓글창을 보면 stomp 구경하기 좋다

socket structure


분홍색은 RESTful 통신, 파란색은 웹소켓 통신

참고자료

HTTP headers in Websockets client API

Websocket - support for custom headers for handshake · Issue #3062 · whatwg/html

CORS - How do 'preflight' an httprequest?

3개의 댓글

comment-user-thumbnail
2021년 1월 8일

같은 주제로 한 2일 동안 고민하고 대안을 찾다가 이 글을 읽게 되었습니다. 다소 막막한 감정에 있었는데 정리해주신 글 읽고나서 다시 생각해보고 싶은 부분이 생겨 너무 감사하다고 말씀드리고 싶네요..!!

감사합니다

답글 달기
comment-user-thumbnail
2021년 6월 24일

좋은글이네요 저같은 경우는
뉴 웹소켓 할때 subprotocol 에다가 토큰을 실는 방법을 이용한 기억이 있습니다.
그렇게 되면 서버측 핸드셰이킹 과정에서 Sec-WebSocket-Protocol 헤더에서 토큰을 취득할수 있더군요 subprotocol이 기본적으로 이런 용도는 아니지만 모질라문서 보면 서버와 클라이언트 간 커스텀 으로 쓴다고 언급되 있네요 ( or may be a custom name jointly understood by the client and the server. )

답글 달기
comment-user-thumbnail
2023년 11월 28일

좋은 글 정말 감사합니다! 읽으면서 정말 많이 공감했네요

답글 달기