2021 05 19
2021-05-19¶
CORS¶
- 참고 1: https://www.youtube.com/watch?v=yTzAjidyyqs
- 참고 2: https://developer.mozilla.org/ko/docs/Web/HTTP/CORS
- 개요 - Cross-Origin Resource Sharing - 교차 출처 요청 및 데이터 전송! - 추가 HTTP 헤더를 사용해서, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에서 알려줌 - 리소스가 자신의 출처와 다를 경우 교차 출처 HTTP 요청을 실행 - fetch API는 "동일 출처 정책"을 따름 - 자신의 출처와 동일한 리소스만을 불러올 수 있음 - 다른 출처의 리소스를 불러오려면 "올바른 CORS 헤더 포함한 응답"을 반환해야 함 - 원래 예전에는 브라우저에서 다른 URL을 가진 서버에 자원 요청을 하지 않았었어... - 그게 위험하다고 생각했거든 - 근데 웹이 너무 발전하면서 프론트/백을 각각의 URL에 배치하면서 문제가 발생한거야
- CORS를 사용하는 요청 1. XMLHttpRequest / Fetch API 2. CSS 내 @font-face에서 교차 도메인 폰트 3. WebGL 텍스쳐 4. drawImage()를 사용해 그린 이미지/비디오 5. 이미지 추출 CSS shapes
- 기능적 개요 - CORS는 허용된 출처를 "서버에서 설명할 수 있는 새로운 HTTP 헤더를 추가"함으로써, 웹 브라우저에서 해당 정보를 읽기를 지원하도록 동작 - 서버 데이터에 side effect를 일으킬 수 있는 HTTP 요청 메서드 (GET을 제외한 메서드) 에 대해서는... - OPTIONS 메서드로 preflight를 하여 지원하는 메서드 요청, 서버의 허가가 떨어지면 실제 요청을 보내도록 함
- Preflight
- simple request가 아닌 것들은 preflight 요청을 보내 실제 요청이 안전한지 확인
- 데이터를 조작하는 (POST/PUT/DELETE) request를 아무곳에서나 받으면 안되자나?
- OPTIONS 요청을 보내 preflight request/response를 받음
- 이후 preflight request가 완료되면 실제 요청을 전송
- 요청 예시
--------------Preflight-------------- OPTIONS /doc HTTP/1.1 Origin: http://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type HTTP/1.1 200 OK Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 ------------------------------------- ---------------Request--------------- POST /doc HTTP/1.1 X-PINGOTHER: pingpong Content-Type: text/xml; charset=UTF-8 Origin: http://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type HTTP/1.1 200 OK Access-Control-Allow-Origin: http://foo.example -------------------------------------
- HTTP 요청 헤더 - Origin : cross-site 접근 요청 or preflight request의 출처 - Access-Control-Request-Method : 실제 요청에서 어떤 HTTP 메서드를 사용할 지 서버에게 알림 - Access-Control-Request-Headers : 실제 요청에서 어떤 HTTP 헤더를 사용할지 서버에게 알리기 위해 preflight에 포함
- HTTP 응답 헤더 - Access-Control-Allow-Origin : 단일 출처를 지정하여 해당 출처가 리소스에 접근하도록 허용 - Access-Control-Max-Age : preflight request 요청 결과를 캐시할 수 있는 시간을 나타냄 - Access-Control-Allow-Credentials : credentials 플래그가 true일 때 요청에 대한 응답을 표시할 수 있는지 - Access-Control-Allow-Methods : 리소스에 접근할 때 허용하는 메서드 지정 - Access-Control-Allow-Headers : 실제 요청시 사용할 수 있는 HTTP 헤더 나타냄
Spring으로 CORS 다루기¶
- 출처: https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
- 개요 - 보안적인 이슈로 인해서 현재 자신의 origin에서 벗어난 AJAX 요청은 금지되어 있다. - Spring MVC는 하이레벨에서 설정을 다룰 수 있도록 지원한다.
- Controller에서 CORS configuration
-
@CrossOrigin을 붙여 CORS를 허락해주도록 할 수 있음 - 필요한 메서드에 추가하거나, 컨트롤러 통채로 등록하거나@CrossOrigin(origins = "http://domain2.com", maxAge = 3600) @RestController @RequestMapping("/account") public class AccountController { @GetMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } }- Spring Security를 사용중이라면, Spring Security level에서 CORS 사용토록 설정하자
- 전역에서 CORS configuration
- JavaConfig에서 다음과 같이 CORS를 활성화 시켜줄 수 있음
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } }- 다음과 같이 특정 필드들에 대해 설정을 활성화 시킬 수 있음
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://domain2.com") .allowedMethods("PUT", "DELETE") .allowedHeaders("header1", "header2", "header3") .exposedHeaders("header1", "header2") .allowCredentials(false).maxAge(3600); }- addMapping : CORS를 적용할 URL 패턴 정의 - allowedOrigins : 자원 공유를 허락할 요청 주소(Origin) 지정 가능 - allowedMethods : 허용할 HTTP method를 지정할 수 있음 - maxAge : 원하는 시간 만큼 preflight 리퀘스트 캐싱해둠 (이 시간 동안은 preflight 검증 안해도 됨!)
- 동작 원리 - CORS 요청 (preflight 요청 포함) 은 자동으로 등록된 HandlerMapping으로 전파 - CorsProcessor 구현체에서 해당 요청을 넘겨받아 CORS 응답 헤더를 작성해줌 - CorsConfiguration 에서 CORS request들이 프로세싱 되어야 하는지 명시할 수 있음 - 헤더 작성, 메서드 등
Cookie vs Session vs Token¶
- 참고 1: https://tansfil.tistory.com/58
- 참고 2: https://dunkey2615.tistory.com/119
- 참고 3: https://interconnection.tistory.com/74
- 개요 - Stateful vs Stateless - Stateful - Client의 상태를 Server가 저장하자 - TCP 프로토콜 - Stateless - Client의 상태를 DB에 저장 - 확장에 매우 유리 - HTTP 프로토콜 - Stateless한 성격을 띠는 자원 기반 아키텍쳐 == REST - 웹에서 서버에 요청을 보내는 작업은 "HTTP 메시지를 보내는 것" - 다양한 인증 방식을 살펴보자
- 계정 정보를 요청 헤더에 넣기 - <설명> - private한 계정 정보를 그냥 HTTP 요청에 넣어서 보내기 - HTTP 에서는 암호화도 안 됨 - <장점> - 인증을 빠르게 테스트 가능 - <단점> - 보안 매우 취약 - 서버에서 요청 올 때마다 id/pw를 통해 유저가 맞는지 인증
- Session/Cookie 방식 - <설명> 1. 사용자가 로그인 2. 서버에서 계정 정보 읽어 사용자 확인 3. 사용자에게 고유한 ID 값을 부여해 세션 저장소에 저장 4. 그에 연결되는 세션 ID 발행 5. 사용자는 서버에서 해당 세션 ID를 받은 후 쿠키에 저장 6. 인증이 필요한 요청마다 쿠키를 헤더에 실어 보냄 7. 서버에서는 쿠키를 받아 세션 저장소와 대조해 대응하는 정보를 가져옴 8. 인증 완료 후 서버는 사용자에게 맞는 데이터 전송해줌 - <장점> - 쿠키는 세션 저장소에 담긴 유저 정보를 얻기 위한 Key - HTTP 요청이 노출되도 쿠키 자체론 무의미한 값임... 따라서 조금 더 안전 - 서버에서는 쿠키 값을 통해 바로바로 어떤 회원인지 확인하기에 용이함 - <단점> - 세션 하이재킹 (인가된 쿠키 훔쳐서 그 사람인거마냥 요청) - HTTPS를 도입해보자 - Session에 유효기간을 부여해주자 - 세션 저장소를 쓰는 건 결국 서버에 추가 공간 필요하단 것
- Cookie - [정의] - 쿠키는 클라이언트 로컬에 저장되는 키/값이 들어있는 작은 데이터 파일 - 사용자 인증이 유효한 시간을 명시할 수 있음 - 유효시간 정해지면 브라우저 꺼져도 인증 유지 - 클라이언트의 상태 정보를 로컬에 저장 했다가 참조 - Response Header에 Set-Cookie 속성을 통해 클라이언트에 쿠키를 만들 수 있음 - Cookie는 사용자가 따로 요청하지 않아도 Request 시에 Request Header를 넣어서 자동으로 서버 전송 - [구성요소] - 이름, 값, 유효시간, 도메인, 경로 - [동작 방식] 1. 클라이언트가 페이지 요청 2. 서버에서 "쿠키를 생성" 3. HTTP 헤더에 "쿠키를 포함시켜 응답" 4. 브라우저 꺼져도 쿠키 만료기간까지 "클라이언트에서 보관" 5. 같은 요청시 HTTP 헤더에 쿠키 같이 보냄 6. 서버에서 쿠키 읽어 이전 상태 변경한다면, 변경된 쿠키 HTTP 헤더에 포함시켜 응답 - [사용처] - 아이디/비밀번호 저장하시겠습니까? - 쇼핑몰 장바구니 - 팝업 "하루동안 보지 않음"
- Session - [정의] - 쿠키 기반의, 서버측에서 사용자 정보 파일 관리 - 클라이언트 구분 위해 세션 ID 발급 - 쿠키보다 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지 - [동작 방식] 1. 클라이언트가 서버 접속시 세션 ID 발급 2. 클라이언트는 세션 ID에 대해 쿠키를 사용해서 저장하고 가지고 있음 3. 클라이언트는 서버에 요청 시, 쿠키의 세션 ID를 서버에 전달함 4. 서버는 세션 ID를 전달받아 세션 ID에 대응되는 클라이언트 정보 가져옴 5. 클라이언트 정보 가지고 서버 요청 처리 - [세션 특징] - 클라이언트에게 고유 ID 부여 - 세션 ID로 클라이언트 구분해서 클라이언트의 요구에 맞는 서비스 제공 - 쿠키보다 보안 굿 - 사용자 많아지면 서버 메모리 많이 차지
- Token 방식 - <설명> - 인증에 필요한 정보들을 암호화시킨 토큰... JWT! - Access Token을 HTTP 헤더에 실어 서버로 보내줌 - 토큰 만들라면... - Header: Header/Payload/Signature를 암호화할 algorithm, 토큰 type 지정 - Payload: 서버에 보낼 데이터 (ID, 유효기간 등) - Signature: Base64 방식으로 인코딩한 header, payload, secretkey를 더한 후 서명 1. 사용자 로그인 2. 사용자 확인 3. JWT 발급 4. 응답 5. 데이터 요청 with JWT 6. JWT 검증 7. 응답 + 요청 데이터 - <장점> - 간편함, 별도 저장소 X - Server Stateless! - 확장성! - 토큰 기반 다른 인증 시스템 접근 가능 - Facebook/Google 모두 토큰 기반 - 선택적으로 이름/이메일 받을 권한도 포함 - <단점> - 이미 발급된 JWT는 돌이킬 수 없음 - 세션/쿠키느 악의적 사용이 되면 세션 그냥 지워버릴 수 있어 - 근데 JWT는 한번 발급되면 유효기간 끝날 때 까지 계속 사용 가능 - 기존의 AccessToken 유효기간 짧게 짧게! - Refresh Token 새로 발급! - Payload가 제한적 - 따로 암호화 안되서 디코딩 쉬움 - 중요 정보 X - JWT는 길어!