-
Notifications
You must be signed in to change notification settings - Fork 0
241120 cookie websocket
로컬 환경에서 배포된 서버 Backend의 WebSocket으로 접근하는 상황이었다. 인증 정보를 쿠키로 전달하려 했지만, WebSocket 연결이 완료되지 못했다.
bet.gateway.ts
handleConnection(client: Socket) {
try {
const cookies = this.jwtUtils.parseCookies(
client.handshake.headers.cookie,
);
const accessToken = cookies["access_token"];
if (!accessToken) {
client.emit("error", {
event: "handleConnection",
message: "엑세스 토큰이 존재하지 않습니다.",
});
client.disconnect(true);
return;
}
...
}bet.gateway.ts에서는 위의 로직으로 accessToken을 검사한다. 그리고 accessToken이 없다면 client.disconnect(true); 를 통해 소켓을 끊어버린다.
브라우저의 개발자 도구에서 응답의 헤더를 확인해보니 Cookie가 비어있었다. 그런 이유로 서버에서 client.handshake.headers.cookie를 조회할 때에도 undefined가 출력되었다.
문제 원인을 찾기 위해 Nginx 설정, CORS 설정, Socket.IO 설정 등을 모두 점검했지만 문제는 해결되지 않았다.
add_header 'Access-Control-Allow-Origin' 'http://localhost:3000';
add_header 'Access-Control-Allow-Credentials' 'true';@WebSocketGateway({
namespace: "api/betting",
cors: {
origin: ["http://localhost:3000", "http://175.45.205.245"],
credentials: true,
},
})CORS, Nginx, Socket.IO 모두 올바르게 설정되었지만 쿠키는 여전히 포함되지 않았다.
BE (user.controller.ts)
res.cookie("access_token", result.accessToken, {
httpOnly: true,
maxAge: 1000 * 60 * 60,
secure: false, // HTTPS를 통해서만 전송되도록 설정
// sameSite: "strict", // 기본 값 LAX
});BE에서 쿠키는 위와 같이 생성된다.
sameSite 설정을 따로 하지 않으면 기본값은 SameSite=Lax 로 설정된다.
그런데 Lax 설정은 예외 상황을 제외하고는 Cross-Origin 요청에서 쿠키를 포함하지 않는다. (예외 상황: GET 요청 + 탑 레벨 네비게이션 (주소창 입력, 링크 클릭 등))
그런 이유로 POST, PUT, WebSocket 연결 등의 요청에서는 Lax 쿠키가 포함되지 않게 된다.
결론은 SameSite 설정 문제였다!
SameSite=None + Secure=true를 사용하여 쿠키가 Cross-Origin 요청에도 포함되도록 설정하는 방법이 있다.
BE (user.controller.ts)
res.cookie("access_token", token, {
httpOnly: true,
secure: true, // HTTPS 필수
sameSite: "None", // Cross-Origin 허용
});하지만 secure: true를 설정하면, HTTPS 환경 필수인데 현재의 서버는 HTTPS로 구성되어 있지 않았다.
쿠키 대신 토큰을 사용하여 인증을 진행한다.
FE
import { io } from "socket.io-client";
const socket = io(SOCKET_URL + options.url, {
auth: {
token: `${access_token}`,
},
});BE
handleConnection(client: Socket) {
const token = socket.handshake.auth.token;
const payload = this.jwtUtils.verifyToken(token);
client.data.userId = payload.id;
}토큰을 이용한 방식으로 에러를 해결했다.
토큰을 이용한 방식으로 처리할 때 가장 처음 custom header를 사용했다.
FE
const socket = io(SOCKET_URL + options.url, {
transports: ["websocket"],
extraHeaders: {
Authorization: `Bearer ${accessToken}`, // 커스텀 헤더 만들기
},
withCredentials: true,BE
handleConnection(client: Socket) {
try {
const authorizationHeader = client.handshake.headers.authorization;
...하지만 http→websocket upgrade를 위한 handshake http 요청에는 custom header를 달 수 없다!
이와 관련된 참고자료
https://socket.io/docs/v4/server-socket-instance/#sockethandshake
Betting duck
- [지호] 베팅 생성 페이지에서 아이콘이 함께 리렌더링 되는 문제 해결
- [정민] 채팅 시스템을 위한 IRC 프로토콜
- [정민, 지호] 소켓 이벤트가 무한리필 되는 문제 해결하기
- [정민, 지호] 소켓 관리 실패로 인한 서버 다운 문제 개선하기
- [석호] 베팅 종료 API 개선하기
- [동교] 베팅 프로세스 흐름도
- [동교] 실시간 베팅에서 레디스 원자성 테스트
- [석호] Redis에서의 트랜잭션
- [석호] Redis를 이용한 메시지 큐 구현(1)
- [석호] Redis를 이용한 메시지 큐 구현(2)
- [동교] Redis에서 O(N) 관련 명령어는 주의하기
- [동교] 베팅덕에 적용한 다양한 캐시 전략
- [동교] 레디스의 메모리 설정 최적화
- [정민] 브라우저에서 렌더가 일어나는 방식
- [정민] Layered frontend application
- [정민] sessionStorage를 이용하여 전역 상태를 관리해도 될까?
- [정민] 왜 css는 내 마음대로 적용이 안될까?
- [공통] 브랜치에서 push 했는데 dev로 바로 병합된다?
- [공통] Cross-Origin WebSocket에서 쿠키 전송 문제: 삽질 기록과 해결
- [FE] Cannot find package 'prettier-plugin-tailwindcss'
- [BE] NestJS에서의 @Injectable() - WebSocketGateway 싱글톤 이슈
- 유저 스토리는 무엇인가?
- GitFlow vs Trunk-based 협업 방식
- The Front End Developer/Engineer Handbook 2024
- 코드 리뷰 in 뱅크샐러드 개발 문화
- Optimize Largest Contentful Paint
- The Looper Mini Web Machine
- Best practices for fonts
- React Folder Structure in 5 Steps [2024]
- Speeding up the JavaScript ecosystem - The barrel file debacle