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에서 따로 어떻게 조정할 수가 없다는 것이다... 이걸 어떻게든 무수한 삽질을 했는데 결국은 오피셜하게 불가능한 일이었다. 보안상의 이유 + 브라우저의 부담 인 것 같은데 이에 대한 논쟁을 찾았다.
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 완료 후 메시지로 한 번 더 체크하는 것보다 크다는 입장이다. 아주 흥미로운데..... 당장 구현하다가 직접 이 문제에 부딪힌 사람으로서 그냥 붙여주면 좋겠다 ㅠㅠㅠㅠ
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에 헤더를 달면서 이런 과정을 추가하기가 빡세다는 것이다.
그래서 무엇이 가장 바람직하냐?
원래 쓰던 유저인증 jwt로 인증을 먼저 한 뒤에 추가적인 30초 안팎의 짧은 토큰을 만들어 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 구경하기 좋다
분홍색은 RESTful 통신, 파란색은 웹소켓 통신
HTTP headers in Websockets client API
Websocket - support for custom headers for handshake · Issue #3062 · whatwg/html
같은 주제로 한 2일 동안 고민하고 대안을 찾다가 이 글을 읽게 되었습니다. 다소 막막한 감정에 있었는데 정리해주신 글 읽고나서 다시 생각해보고 싶은 부분이 생겨 너무 감사하다고 말씀드리고 싶네요..!!
감사합니다