1. 서론
최근 회사 동료분과 사내 행사 관련한 웹사이트를 만드는 토이프로젝트를 했습니다.
백엔드파트인 저는 api개발을, 웹파트인 동료분은 프론트를 맡아서 각각 개발한 후 웹에 api를 붙이려는 계획이었는데요,
간단한 사이트니까 주말동안 뚝딱 만들어오죠?ㅋㅋ 라며 경거망동하게 입을 놀렸던 스스로를 반성하게 되는 보람된 시간이었습니다.
사실 개발보다는 배포과정에서 이슈가 많았는데요, 그 얘기는 나중에 또 적도록 하겠습니다.
아무튼 우여곡절 끝에 잘 배포를 했고 http test에서 response가 잘 나오는 걸 확인하고 동료분께 api를 드렸습니당.
"혜리님 오류나는데요 ㅠ"
"네..?ㅠ 왜요..?ㅠ"
그냥 브라우저 상에서 api를 직접 호출할 때는 잘 작동을 하지만
local web 개발 환경에서는 response가 빈값으로 돌아오는 이상한 오류였습니다,
UserInfo.vue:31 Error: Network Error
at t.exports (createError.js:16)
at XMLHttpRequest.p.onerror (xhr.js:81)
다른 웹파트 동료분께 여쭤보고 구글링을 해보니 CORS가 관련된 문제였습니다.
사실 저는 CORS가 뭔지도 몰랐습니다.
그래서 CORS가 뭐하는 놈인지 찾아보았습니다.
2. CORS
교차 출처 리소스 공유(Cross-origin resource sharing, CORS), 교차 출처 자원 공유는 웹 페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 구조이다.[1] 웹페이지는 교차 출처 이미지, 스타일시트, 스크립트, iframe, 동영상을 자유로이 임베드할 수 있다.[2] 특정한 도메인 간(cross-domain) 요청, 특히 Ajax 요청은 동일-출처 보안 정책에 의해 기본적으로 금지된다.
CORS는 교차 출처 요청을 허용하는 것이 안전한지 아닌지를 판별하기 위해 브라우저와 서버가 상호 통신하는 하나의 방법을 정의한다.[3] 순수하게 동일한 출처 요청보다 더 많은 자유와 기능을 허용하지만 단순히 모든 교차 출처 요청을 허용하는 것보다 더 안전하다. CORS의 사양은 원래 W3C 권고안으로 출판되었으나[4] 해당 문서는 구식(obsolete)인 상태이다.[5] 현재 CORS를 정의하면서 활발히 유지보수된 사양은 WHATWG의 Fetch Living Standard이다.[6]
위키피디아에서 긁어왔는데 무슨 말인지 잘 모르겠군요...
다시 정리하자면 CORS란 Cross Origin Resource Sharing 의 줄임말로 Cross-site Http Request를 가능하게 하는 표준 규약입니다. Http 요청은 기본적으로 cross-site 요청이 가능합니다. 즉 haeree.com 도메인에서 dongdong.net의 리소스를 요청할 수 있다는 말입니다. rest api를 널리 사용하는 요즘에는 엄청 당연하다고 생각이 되지만 이런 요청이 불가능한 상황이 있습니다.
에러 로그에 보면 XMLHttpRequest에서 오류가 난 것을 볼 수가 있는데요, XMLHttpRequest는 보안상의 이유로 same-origin 정책을 따른다고 합니다. 즉 XMLHttpRequest를 사용하는 웹 어플리케이션은 어플리케이션과 동일한 출처(프로토콜, 호스트, 포트) 의 리소스 만 요청할 수 있습니다! 오.. 엄청 귀찮은 정책이네요...
저희 토이프로젝트 같은 경우는 api는 elastic beanstalk으로, 웹은 s3로 배포를 해서 서로 도메인이 달랐습니다.
이럴 때 필요한 것이 CORS입니다. CORS는 헤더에서 브라우저와 서버가 상호 통신하는 방법을 결정 한 후에 해당 cross-site 요청이 안전한지 아닌지를 판별합니다.
쉽게 얘기해서 서버에서 응답을 할 때 헤더에 요청을 허용할 도메인, http 메소드 등을 함께 적어주면 에러가 나지 않는다는 말입니다. 얏호!
3. rack-cors
ruby에는 cors 설정을 도와주는 gem이 있습니다.
# Gemfile.rb
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'
rails 프로젝트를 api 옵션으로 생성하면 위의 코드가 주석처리 되어있습니다. 우리는 이 코드를 주석처리만 해주면 됩니다.
# config/application.rb
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :options]
end
end
그리고 application.rb 파일에 위와 같은 코드를 추가해주시면 됩니다.
이 코드는 모든 도메인에 대해 cross-site 요청을 허용합니다. 빠르고 편하지만 보안에는 취약하겠죠.
# config/application.rb
config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:3000', '127.0.0.1:3000',
/\Ahttp:\/\/192\.168\.0\.\d{1,3}(:\d+)?\z/
# regular expressions can be used here
resource '*', headers: :any, methods: [:get, :post, :options]
end
end
어떤 도메인으로 특정하고 싶다면 위처럼 콤마로 구분을 해서 도메인 주소를 적어주시면 됩니다. 정규식을 이용하하면 도메인을 특정하지 않고 패턴으로 도메인을 설정할 수 있습니다.
use Rack::Cors do
allow do
origins 'localhost:3000', '127.0.0.1:3000',
/\Ahttp:\/\/192\.168\.0\.\d{1,3}(:\d+)?\z/
# regular expressions can be used here
resource '/file/list_all/', :headers => 'x-domain-token'
resource '/file/at/*',
methods: [:get, :post, :delete, :put, :patch, :options, :head],
headers: 'x-domain-token',
expose: ['Some-Custom-Response-Header'],
max_age: 600
# headers to expose
end
allow do
origins '*'
resource '/public/*', headers: :any, methods: :get
# Only allow a request for a specific host
resource '/api/v1/*',
headers: :any,
methods: :get,
if: proc { |env| env['HTTP_HOST'] == 'api.example.com' }
end
end
출처: https://github.com/cyu/rack-cors
위처럼 복잡한 로직도 설정이 가능합니다.
에러 로그에 자세한 정보가 없어서 당황했는데 해결방법은 생각보다 쉬웠던 이슈였습니다.
'Ruby on Rails' 카테고리의 다른 글
Ruby Symbol에 대해서 (1) | 2020.10.11 |
---|---|
windows에서 Ruby on Rails 개발환경 구축하기 (wsl + rubymine) (1) | 2020.01.08 |
named scope안에서 last 메소드를 사용했을 때의 문제 해결 (0) | 2019.11.28 |
Ruby nil 처리하기 (0) | 2019.11.14 |
Named scope로 db 조회를 최적화 해보자 (0) | 2019.10.31 |
댓글