서버가 리퀘스트를 보낸 유저가 누구인지 파악하는 과정
인증을 하기 위해서는 먼저 서버에 유저에 관한 정보가 저장되어 있어야 한다.
유저를 나타내는 모델이라는 것을 만든다.
유저 클래스를 만든다고 생각하면 됨
POST 보내서 유저를 생성한다. (id값으로 유저 구분)
인증서로 유저 구분, 인증서는 만료 기간이 있음
서버 리스폰스나, 클라이언트 코드에 따라 브라우저에 저장되는 작은 단위의 문자열 파일들
이름, 값, 속성이 들어있다. (세미콜론으로 구분되어 있다.)
속성에는 서버에서 정한 만료일이나 보안 설정 등의 다양한 값이 있다.
서버에서 인증서를 리스폰스의 Set-Cookie
헤더에 담아서 클라이언트에 보내면 쿠키 이름과 값을 저장하고 Set-Cookie
에 있는 다양한 값들을 저장한다.
이렇게 저장된 쿠키는 이후 브라우저가 리퀘스트를 보낼 때 자동으로 Cookie
라는 헤더에 추가돼서 보내진다.
서버는 리퀘스트에서 Cookie 헤더를 확인하고 이 인증서를 통해 어떤 유저가 보냈는지 알아낸다.
브라우저에 저장된 모든 쿠키가 모든 리퀘스트에 보내지는 것은 아니고, 쿠키를 설정한 웹사이트에만 보내진다.
ex) 페이스북에서 브라우저에 쿠키를 저장했다고 할 때, 브라우저에서 리퀘스트를 보낼 때, 구글로 보내는 리퀘스트에는 쿠키가 추가되지 않는다.
같은 루트 도메인 사이에서는 쿠키가 공유될 수 있다.
ex) google.com, mail.google.com 에서 공유할 수 있다.
서버 리스폰스의 Set-Cookie를 통해서 문자열 인증서를 쿠키를 저장하고 리퀘스트에 저장한 쿠키를 추가해서 전송하는 부분은 따로 코드를 작성하지 않아도 브라우저에서 자동으로 해준다. -> 조금 더 편리하게 인증 구현 가능!
쿠키 인증을 사용할 때 보안 문제를 줄일 수 있는 설정들
서버에서 리스폰스를 보낼 때 이 설정을 추가해주면 HTTP보다 보안에 강한 HTTPS를 사용할 때만 클라이언트에 서버로 쿠키가 보내진다.
HTTPS를 사용하면 항상 리퀘스트와 리스폰스가 암호화되기 때문에 누군가 중간에 리퀘스트를 가로챘을 때 정보 유출을 줄일 수 있다.
Secure 설정은 리스폰스 Set-Cookie 헤더에 이름-값 쌍 뒤 ;과 Secure 키워드를 사용해서 적용할 수 있다.
Set-Cookie: cookie_name=cookie_value; Secure;
이 설정을 추가하면 클라이언트가 자바스크립트 코드로 해당 쿠키에 접근할 수 없게 된다. 자바스크립트 코드로는 가지고 올 수 없고, 그냥 쿠키를 설정한 웹사이트에 리퀘스트로 보낼 수 있게 되는 것.
코드로 쿠키에 접근할 수 없으면, 악의적 클라이언트가 개인 정보에 직접 접근하는 걸 막을 수 있다.
HttpOnly 설정은 리스폰스 Set-Cookie 헤더에 이름-값 쌍 뒤 ;과 HttpOnly 키워드를 사용해서 적용할 수 있다. (Secure와 HttpOnly 설정이 동시에 적용된 예시)
Set-Cookie: cookie_name=cookie_value; Secure; HttpOnly;
설정은 Cross site request forgery의 약자, CSRF(또는 XSRF)라는 공격을 예방할 수 있는 설정이다.
CSRF는 일반 사이트 A와 악의적 사이트 B가 있을 때, B 웹 페이지에서 브라우저에 저장된 쿠키를 가지고 사이트 A 서버로 리퀘스트를 보내는 공격이다.
SameSite를 Strict로 하면 다른 도메인에서 리퀘스트를 보낼 때 쿠키가 가는 걸 아예 방지할 수 있다.
-> 리퀘스트를 보내는 클라이언트와 이를 받는 서버의 도메인이 서로 같을 때만 쿠키가 간다.
하지만 URL을 직접 쳐서 방문하는 경우가 아닌, 이메일이나, 메시지, 심지어 다른 페이지에 있는 링크를 통해서도 페이지를 방문하는 경우에 쿠키가 가지 않는 문제가 생긴다.
이러한 경우에는 다시 로그인을 해야 하는데, 민감한 정보가 아닐 경우에 과한 조치일 수 있다.
이럴 때 사용하는 설정이 Lax이다. 이 설정을 하면 링크를 통해 사이트를 직접 방문할 때는 쿠키가 보내진다.
SameSite 설정을 None으로 하면 아무런 제한 없이 브라우저에서 보내는 모든 리퀘스트에 쿠키가 붙어서 간다.
SameSite 설정을 None로 할 때는 보안 문제 때문에 항상 Secure 설정을 추가해야 한다. 추가하지 않으면 특정 브라우저들은 보안이 취약하다고 판단하고 쿠키를 저장하지 않는다.
SameSite 설정은 리스폰스 Set-Cookie 헤더에 이름-값 쌍 뒤 ;과 SameSite 키워드, 그리고 원하는 쿠키 사용 범위 키워드, None, Lax, Strict를 사용해서 적용할 수 있다. (Secure, HttpOnly, 그리고 SameSite=Lax 설정이 동시에 적용된 예시)
Set-Cookie: cookie_name=cookie_value; Secure; HttpOnly; SameSite=Lax;
수동으로 인증을 관리하고 싶을 때,
서버는 리스폰스의 바디에 인증서를 추가한다.
자바스크립트 코드로 이 인증서를 직접 저장한다. (쿠키는 리스폰스의 set-cookie 헤더 뿐만 아니라 클라이언트의 자바스크립트 코드로도 저장, 접근 가능)
쿠키를 전송이 아니라 단순히 저장 수단으로 사용하게 되는 것
쿠키든,로컬스토리지든 인증서를 저장해서 읽어오면 되는 것
리퀘스트에 Authorization 헤더를 추가하고 그 뒤에 인증서를 붙여주면 된다.
브라우저가 자동으로 해주는 것이 아니라 개발자가 쓴 자바스크립트 코드로 저장된 인증서를 읽어와서 헤더에 붙여줘야 한다.
서버는 Authorization 헤더에 붙은 인증서를 확인해서 누가 보낸건지 알아낸다.
서버가 저장하는 사이트 방문자들에 대한 기록
클라이언트가 쿠키로 세션 id를 보내주기 때문에 서버는 매칭되는 정보를 찾아서 리퀘스트를 보낸 유저를 특정지을 수 있다.
특정 시간이 지나거나 로그아웃 리퀘스트를 보내면 서버는 세션 id를 만료 처리한다.
그러면 기존 세션 id를 아무리 보내도 만료되었기 때문에 다시 로그인을 해야 한다.
인증 토큰이라는 것을 보내서 리퀘스트를 보낸 사용자를 파악한다.
인증 토큰: 유저에 대한 정보를 암호화한 문자열, 접근토큰이라고도 불림
토큰 자체를 해석해서 사용해도 되기 때문에 리퀘스트가 올 때마다 저장하지 않아도 된다.
서버가 리스폰스 바디에 토큰을 담아서 클라이언트에 전달하는게 일반적이고,
Authorization 헤더로 돌려 받는게 더 일반적이다.
Authorization: Bearer 토큰으로 암호화한 문자열
서버는 비밀 키를 사용해서 해석
만료 시간만 안 지나면 로그인 상태 유지, 유저가 로그아웃 하고 싶으면 클라이언트에서 직접 저장한 토큰을 삭제해주면 된다.
HTTP 헤더 이름과 값들은 미리 정해진 256개의 문자 (ASCII)만 사용해야 한다.
ASCII에 포함되지 않는 문자들은 ASCII에 포함된 문자로 바꿔서 사용해야 한다.
이렇게 데이터를 여러 곳에서 쉽고 안정적이게 사용하기 위해 통일된 형식으로 바꾸는 걸 인코딩이라고 부른다.
사실 인증을 할 때는 인증서의 개념을 사용하지 않아도 된다 -> 기본 인증
기본 인증은 리퀘스트의 Authorization 헤더를 사용하는데, 토큰 인증을 할 때 Authorization 헤더 뒤에 Bearer 또는 Token 그리고 뒤에 토큰을 붙였던 것과 비슷하게 Authorization 헤더 뒤에 Basic, 그리고 이메일과 비밀번호를 :로 이어서 붙여주면 된다.
Authorization: Basic email:password
참고로 토큰 기반 인증과 기본 인증에서 Bearer/Token 또는 Basic을 붙여주는 건 서버에게 뒤에 붙이는 인증 정보의 종류를 서버에 알려 주기 위한 방법이다. Authorization 헤더 인증을 사용한다면 꼭 추가해 줘야 한다.
기본 인증은 유저를 인증하는 자체에 대해서는 부족함이 없지만 보안 문제 때문에 요즘은 거의 사용하지 않는다.
모든 리퀘스트에 이메일과 비밀번호 조합같은 민감한 데이터를 보내면 중간에서 공격자가 가로챌 위험이 커지고, 로그인 상태를 유지할 때, 이메일과 비밀번호를 브라우저에 저장해 놓는 경우에도 노출될 위험이 있기 때문에 안전하지 않다.
Access 토큰의 만료 기간을 길게 잡으면 토큰을 누군가 가로챘을 때 더 오랫동안 특정 권한을 갖는 유저 행세를 할 수 있고, 만료 기간을 짧게 잡으면 이메일과 비밀번호로 인증을 너무 자주 해야 돼서 귀찮고 위험할 수 있다.
이 문제를 어느 정도 해소하기 위해 때때로 refresh 토큰을 같이 사용한다.
refresh 토큰은 access 토큰이 만료됐을 때, 이메일 비밀번호를 사용하지 않고 access 토큰을 새롭게 발급받는 데 사용되는 토큰이다.
서버는 클라이언트에서 access와 refresh, 두 가지의 토큰을 보내줄 수 있다.
access 토큰을 사용하다가 만료가 돼서 더 이상 리퀘스트를 인증할 수 없게 되는 경우가 생겼을때 클라이언트는 access 토큰을 새롭게 발급받는 URL에 새로운 GET 리퀘스트를 보낸다. 이때 body에 refresh 토큰을 함께 보내는데, 그럼 서버는 refresh 토큰이 유효한 걸 확인한 후, 새로운 access 토큰을 발급한 후, 리스폰스로 클라이언트에게 돌려준다.
일반적으로 refresh 토큰이 access 토큰보다 만료 기간이 더 길다.
access 토큰의 만료 기간이 짧기 때문에 서버로 토큰을 보낼 때 예를 들어 누군가 이 정보를 가로챈다고 해도 최대 10분까지만 다른 유저 행세를 할 수 있다.(누군가 가로챈다고 해도 그렇게 오래 사용할 수는 없으니 조금 더 안전)
refresh 토큰은 access 토큰이 만료됐을 때만 보내면 되기 때문에 훨씬 더 적게 보내도 되고 그만큼 누가 빼돌릴 확률이 줄어든다.
JSON 형식의 데이터를 문자열로 인코딩한 토큰
Header / Payload / Signiture 라고 나뉘어 있다.
- Header: 토큰 자체에 대한 데이터 저장 (알고리즘, 토큰 형식 등)
- Payload : 토큰이 실질적으로 저장하려는 정보 (완료 시간, 발급 시간, 토큰 고유값, 유저 아이디 등..)
저장하고 싶은 데이터 종류의 제한은 없다.
최대한 짧은 게 좋기 때문에 꼭 필요한 내용들만 간결하게 저장하는 것을 권장
공식 이름 최대한 활용
- Signiture : 토큰을 믿을 수 있는지 확인하기위한 데이터 저장
헤더,페이로드, 시크릿 키 3가지 정보가 합쳐져서 만들어진다.
페이로드, 시크릿 키를 헤더에 나온 알고리즘을 통해서 암호화
클라이언트에서 받은 헤더와 페이로드, 그리고 서버만 알고 있는 비밀키를 사용해서 직접 시그니처 부분을 만들고, 토큰의 시그니처와 비교해서 확인한다.
헤더와 페이로드는 아무나 볼 수 있다.
토큰 기반 인증의 첫 번째 장점은 효율성이다.
세션 기반 인증을 사용하면 서버는 항상 로그인 세션 정보를 저장하며, 매 리퀘스트의 유저가 누구인지를 이 정보와 비교해야 하는데 이를 하기 위해서는 용량과 시간이 든다.
반면 토큰 기반 인증은 어딘가 저장한 데이터와 비교하는 것이 아니라 토큰 자체 내용을 해석하기만 하면 되기 때문에 더 효율적으로 작동할 수 있다.
토큰 기반 인증은 세션 기반 인증보다 조금 더 유연하게 사용될 수 있다.
(토큰을 발행하는 방법이 똑같고, 시크릿 키만 있으면, 발행을 한 곳과 확인을 하는 곳이 달라도 됨)
예를 들어 같은 유저 데이터베이스를 사용하는 여러 서비스들이 있고, 이 사이트들이 같은 방식과 키를 사용해서 토큰을 발행한다면, 한 사이트에서 제공한 토큰을 가지고 있으면, 다른 서비스가 그걸 해석해서 유저를 파악할 수 있다는 것이다.
세션 정보와 같이 서버가 "상태" 정보를, 예를 들어 유저가 로그인을 했는지 안 했는지 저장하고 있을 때, stateful 하다고 표현한다.
REST에 부합하기 위해서는 서버가 상태 정보를 저장하지 않는, stateless 한 특성이 있어야 한다.
(서버는 클라이언트에서 보내는 정보만으로 충분히 상태를 파악할 수 있어야 함)
이 기준에서 살펴보면 RESTful한 API 서버를 만들고 있다면 세션 기반 인증보다 토큰 기반 인증이 더 어울린다고 할 수 있다.
세션 기반 인증의 장점 중 하나는 바로 서버에서 세션 데이터를 따로 관리를 하기 때문에 특정 세션을 손쉽게 무효화할 수 있다는 점이다.(세션을 관리하는 테이블에 가서 상태를 만료로 바꾸거나 만료일을 지금 당장으로 해버리기)
하지만 토큰 기반 인증을 사용하면 따로 서버가 상태 정보를 저장하지 않기 때문에 특정 토큰을 무효화하는 게 더 복잡하다. 이것은 꽤 큰 문제들로 이어질 수 있는데, 예를 들어 금융 서비스에서 누군가 유저 토큰을 가로챘는데 이걸 바로 무효화할 수 없다면 저금해놓은 돈을 뺏기는 문제가 생길 수도 있다. 물론 이중 비밀번호나, OTP라든지 이걸 방지하기 위한 방법들이 있지만, 세션 기반 인증을 사용하면 한 가지 안전장치를 더 손쉽게 사용할 수 있다.
리퀘스트가 어떤 권한이 있는지 판단
리퀘스트 내용을 요청할 권한이 있는지
Authorization 헤더는 인증을 위한 헤더이지만 이름을 잘못지은 것이고, 이름을 바꾸면 혼란이 일어날까봐 그냥 쓰는 것.