티스토리 뷰
이번 주에 발생했던 따끈따끈한 문제
문제 상황은 이렇다.
1. 비로그인 사용자를 구분하기 위한 id를 부여하고 싶다.
2. id를 어디서 부여하고 저장할까? - 보안상의 이유로 단순 로컬스토리지보다는 쿠키에 저장하는 게 좋을 것 같다.
3. 누가 쿠키를 발행할 것인가? fe가 가능할 것 같다고해서 별 생각 없이 ok
4. fe 서버에서 쿠키에 강제로 domain을 부여하니까 localhost에서 쿠키를 사용할 수 없는 문제 발생
5. 결국 서버에서 쿠키를 만들기로 하는데....
결론부터 이야기하자면 모두가 쿠키에 대한 제대로된 지식이 없어서 발생한 문제였다.
정확히는 Thirdparty cookie와 cookie의 도메인에 대한 지식이 부족했다.
어떤 문제가 발생했는지 차근 차근 밟아나가보자.
이슈 1. FE에서 발행한 쿠키의 도메인 문제
우리 서비스의 FE는 CSR로 동작한다. 그래서 FE도 별도의 서버를 갖고 있기 때문에 쿠키를 발행할 수 있다.
문제는 BE 서버와 FE의 서버의 기본 도메인이 다르다는 것이다.
BE 도메인 : api.xxx.com
FE 도메인 : web.xxx.com
이런 경우에 쿠키를 주고 받는다면 Thirdparty cookie에 대한 문제를 고려해야 한다.
용어에 대해 가볍게 핥고 간다면,
도메인이 다른 Origin에서 발행한 쿠키를 Thirdparty cookie
도메인이 같은 Origin에서 발행한 쿠키를 Firstparty cookie 라 한다.
아무튼, FE에서 쿠키를 만들었기 때문에 BE에서 쿠키를 받으려면 요청에 쿠키가 포함되어야 한다.
하지만 쿠키를 발행한 도메인이 다들 경우, 요청에 쿠키를 포함시켜 서버로 보내게 된다면
서버는 쿠키의 정보를 읽을 수 없다.
도메인이 다른 쿠키를 서버의 요청에 포함 시킬 경우 Credentialed Reqeust, 즉 자격증명된 요청을 보내야 하기 때문이다.
설정은 어렵지 않다.
우선 FE에서 요청을 보낼 때, 헤더의 withCredentials 옵션을 true로 설정해야한다.
FE 코드는 잘 모르기 때문에 별도의 코드는 첨부하지 않았다.
여기서 또 주의할 점은, preflight request를 발행하지 않는 Simple GET request에 대해서는 서버가 요청을 받아도 response를 생성하지 않는다는 점이다.(확인 필요)
BE도 자격증명된 요청을 받기 위해서 CORS 옵션을 만져줘야 한다.
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${allowed-origins}")
private List<String> allowedOrigins;
....
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.allowedOrigins(allowedOrigins.toArray(new String[allowedOrigins.size()]))
.allowedMethods("*")
.allowCredentials(true);
}
}
두 가지 옵션 설정이 필요하다.
와일드 카드 설정을 하지 않은 명시적인 Origin 허용 설정과 Credential 설정을 true로 처리해 주어야 한다.
위 코드는 결국 헤더의 Access-Control-Allow-Credentials : true로 설정하고, Access-Control-Allow-Origin : [명시적 URL] 로 작성하는 것이다.
이런 식으로 처리하면, Thirdparty cookie를 주고 받을 수 있다.
여기까진 문제가 없었는데, FE에서 localhost일 때 도메인 설정에 문제가 있었다.
(로컬에서 테스트할 시 dev 서버에서 쿠키를 못 받고, dev에서 받으면 로컬에서 테스트가 불가한 현상)
결국 FE에서 쿠키를 핸들링하는 건 좋지 않다 생각해(본래 쿠키의 목적에도 맞지 않았다)
서버에서 쿠키를 만들어 주기로 했다.
이슈 2. BE에서 발행한 쿠키의 세부 설정 문제
처음에 언급했듯이 BE 개발자(나)는 Thirdparty cookie의 의미를 정확하지 파악하지 못했었다.
그래서 일반 SSR에서 처리하듯이 쿠키를 그냥 내려주면 되겠지? 라고 일반 쿠키를 생성해줬다.
public Cookie setCookie(String guestId) {
int maxAge = 24 * 60 * 60 ;
Cookie guestCookie = new Cookie("guest_id", guestId);
guestCookie.setMaxAge(maxAge);
guestCookie.setPath("/");
return guestCookie;
}
이러면 FE에서 쿠키를 받을 수 있을까?
당연히 No다.
domain 설정을 하지 않고 내리면, BE의 기본 도메인인 api.xxx.com으로 쿠키가 생성된다.
때문에, 도메인이 다른 FE에서는 당연히 쿠키가 생성되지 않는다.
(정확히는 쿠키가 생성되긴 하는데, 원하는 페이지에서 보이지 않는다.)
바로 도메인 설정 후 쿠키를 내려줬다.
public Cookie setCookie(String guestId) {
int maxAge = 24 * 60 * 60; // 1일로 설정
Cookie guestCookie = new Cookie("guest_id", guestId);
guestCookie.setMaxAge(maxAge);
guestCookie.setDomain(domain);
guestCookie.setPath("/");
return guestCookie;
}
이러면 FE에서 쿠키가 확인 가능할까?
이번에도 No다.
브라우저에서는 Thirdparty cookie를 보안적인 이유 때문에 기본적으로 차단을 하고 있다.
때문에 Thirdparty cookie를 사용하기 위해서는 특별한 설정을 해줘야한다.
Same Site와 secure설정이다.
불행하게도 Java의 기본 쿠키는 위 설정 중 SameSite 옵션을 제공하지 않는다.
그래서 스프링에서 제공하는 ResponseCookie를 이용해 쿠키를 설정하고, 헤더에 붙여 줬다.
public String setGuestCookie(String guestId) {
int maxAge = 24 * 60 * 60 ; // 1일로 설정
return ResponseCookie.from("guest_id", guestId)
.domain(domain)
.maxAge(maxAge)
.sameSite("None")
.secure(true)
.path("/")
.build().toString();
}
httpServletResponse.addHeader("Set-Cookie", setGuestCookie(id));
이러면 서로 다른 도메인의 쿠키(Thirdparty cookie)를 FE에서 확인할 수 있게 된다.
마치며
웹서비스에 대한 지식이 부족했던걸 통감했던 문제였다.
애써 외면했던 CORS와 브라우저와의 요청에 대한 지식을 어느정도 채울 수 있었던 기회가 됐다.
CORS에 문제와 보안에 대한 정보는 워낙 많은 곳에서 정리하고 있어서 굳이 다룰 지는 잘 모르겠다.
참고자료
'개발 > 개발팁' 카테고리의 다른 글
- Total
- Today
- Yesterday
- openAI API
- AOP
- GIT
- 오블완
- terraform
- Log
- serverless
- cache
- ChatGPT
- 후쿠오카
- MySQL
- lambda
- S3
- 스프링부트
- 람다
- OpenFeign
- Spring
- CloudFront
- docker
- AWS EC2
- java
- AWS
- 티스토리챌린지
- OpenAI
- Elastic cloud
- JWT
- EKS
- elasticsearch
- springboot
- Kotlin
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |