🐕 쿠키
Domain | 쿠키에 저장할 도메인 정보 |
Path | 서버가 라우팅할 때 사용하는 경로 |
MaxAge or Expires | 쿠키의 유효기간 정하는 옵션 |
Secure | 프로토콜에 따른 쿠키 전송 여부 옵션 |
HttpOnly | 자바스크립트로 쿠키에 접근 가능한지 결정하는 옵션 |
SameSite | Cross-Origin 요청을 받은 경우 요청에서 사용한 메소드와 해당 옵션의 조합을 기준으로 서버의 쿠키 전송 여부를 결정 다양한 옵션이 있음 |
Domain
쿠키옵션에서 도메인은 포트 및 서브 도메인 정보, 세부 경로를 포함하지 않습니다.
서브 도메인이란 www 같은 도메인 앞에 추가로 작성되는 부분을 말합니다.
따라서 요청해야할 URL이
http://www.localhost.com:3000/users/login
이라고 가정하면
localhost.com
여기서 쿠키에 저장될 도메인은 다음과 같습니다.
서브 도베인, 포트, 세부경로를 포함하지 않고 딱 도메인 주소만 저장하는거네요!
만약 쿠키옵션에 도메인 정보가 존재한다면 클라이언트에서는
쿠키의 도메인 옵션과 서버의 도메인이 일치해야지만 쿠키를 전송할 수 있습니다.
따라서 naver에서 받은 쿠키를 google에 보내는 일같은 것은 이 시점에서 막아집니다.
도메인을 기준으로 비교하면 되기때문에 다른 정보는 굳이 저장할 필요가 없는거네요
path
path는 세부 경로로 서버가 라우팅할 때 사용하는 경로를 의미합니다.
path 옵션의 특징은 설정된 경로를 포함하는 하위 경로로 요청을 하더라도
쿠키를 서버에 전송할 수 있습니다.
즉 path 가 /users이고 요청하는 세부경로가 /users/zansol 인 경우라면
쿠키전송이 가능합니다.
다만 path 옵션을 만족시키지 못하는 요청은 서버로 쿠키를 전송하지 못합니다.
당연함
MaxAge or Expires
쿠키가 유효한기간을 정하는 옵션입니다.
만약 쿠키가 영원히 남아있으면 탈취되기도 쉬워지기 때문에
유효기간을 설정하는 것은 보안 측면에서 중요합니다.
MaxAge | 쿠키가 유효한 시간을 초단위로 설정하는 옵션입니다 |
Expires | MaxAge와 비슷하지만 언제까지 쿠키가 유효할지 날짜를 지정해줄 수 있습니다. 이때 옵션의 값은 클라이언트의 시간을 기준으로 하며 지정된 시간, 날짜를 초과하면 쿠키는 파괴됩니다. |
쿠키는 위 옵션의 여부에 따라 세션 쿠키와 영속성 쿠키로 나누어집니다.
세션 쿠키 | MaxAge 또는 Expires 옵션이 없는 쿠키로 브라우저가 실행중일 때 임시로 사용하는 임시 쿠키입니다. 브라우저를 종료하면 해당 쿠키는 삭제됩니다. |
영속성 쿠키 | 브라우저의 종료 여부와 상관없이 MaxAge 또는 Expires에 지정된 유효시간 만큼 사용가능한 쿠키입니다. |
Secure
사용하는 프로토콜에 따른 쿠키의 전송 여부를 결정하는 옵션입니다.
만약 Secure 옵션이 true로 설정되면 HTTPS를 사용하는 경우에만 쿠키를 전송할 수 있습니다.
반대로 Secure가 false면 HTTP에도 쿠키를 전송할 수 있습니다.
단 도메인이 localhost인 경우에는 HTTPS가 아니여도 쿠키 전송이 가능하며
이는 개발 단계에서 localhost를 많이 사용하기에 생긴 예외입니다.
HttpOnly
자바스크립트로 브라우저의 쿠키에 접근이 가능한지 여부를 결정합니다.
해당 옵션이 true로 설정된 경우 자바스크립트로 쿠키에 설정이 불가능하게 됩니다.
옵션을 명시하지 않은 경우에는 기본적으로 false가 지정됩니다.
이 옵션이 false인 경우에는 document.cookie를 이용해
자바스크립트로 쿠키에 접근할 수 있으므로 쿠키가 탈취될 수 있습니다.
SameSite
Cross-Origin 요청을 받은 경우 요청에서 사용한 메소드와 해당 옵션의 조합을 기준으로
서버의 쿠키 전송 여부를 결정하게 됩니다.
Lax | Cross-Origin 요청이라면 GET 메소드에 대해서만 쿠키를 전송할 수 있습니다. |
Strict | 단어 그대로 가장 엄격한 옵션으로 Cross-Origin이 아닌 same-site인 경우에만 쿠키를 전송할 수 있습니다. |
None | Cross-Origin에 대해 가장 관대한 옵션으로 항상 쿠키를 보내줄 수 있습니다. 다만 쿠키 옵션 중 Secure 옵션이 필요합니다. |
쿠키를 이용한 상태 유지
쿠키의 특성을 이용하여 서버는 클라이언트에 인증정보를 담은 쿠키를 전송하고
클라이언트는 전달받은 쿠키를 서버에 요청과 함께 전송하는 방식으로
stateless한 인터넷 연결을 stateful하게 유지할 수 있습니다.
하지만 기본적으로 쿠키는
1. 오랜시간 유지 가능하고
2. HttpOnly 옵션을 true로 주지 않으면 자바스크립트로 쿠키에 접근할 수 있습니다.
따라서 쿠키에 민감한 정보를 담는 것은 위험합니다.
이런 인증 정보를 이용해 공격자가 유저인척 서버에 요청을 보내면 서버는
누가 요청을 보냈는지 의심하지 않고 인증된 유저의 요청으로 취급하여
응답을 보내게될것이고 이때 개인정보와 같은 민감한 정보를 탈취 당할 위험이 있습니다.
👻세션(Session)
세션은 쿠키를 이용하는 방식입니다. 즉 세션과 쿠키는 완전히 별개가 되는 것은 아닙니다.
다만 쿠키는 접속 상태의 저장을 클라이언트에 하고 세션은 서버에 한다는 차이점이 있습니다.
따라서 세션은 요청을 한 유저가 신뢰할 수 있는 유저인지
서버에서 추가로 확인을 할 수 있다는 장점이 있습니다. 즉 쿠키보다 보안적인 측면에서
유리한 부분이 있지만 서버는 서버에 유저에 대한 정보를 저장하게되므로
서버의 부담이 좀 더 커질 수 있습니다.
서버는 사용자가 인증에 성공했음을 알고 있어야 하며
클라이언트는 인증 성공을 증명할 수단을 갖고 있어야 합니다.
하지만 http는 stateless하기때문에 이 문제를 해결하기 위해서
쿠키, 세션 등의 방식을 사용합니다.
사용자가 인증에 성공한 상태를 세션이라고 부릅니다.
서버는 일종의 저장소(in-memory , 세션스토어)에 세션을 저장하며
세션이 만들어지면 각 세션을 구분할 수 있는 세션 아이디도 만들어집니다.
이 세션아이디를 쿠키를 통하여 클라이언트에게 전달을 하며
웹사이트에서 로그인을 유지하기 위한 수단으로 쿠키를 사용합니다.
당연히 쿠키에는 서버에서 발급한 세션아이디가 저장되게 됩니다.
세션 방식으로 로그아웃을 구현하려고 할 때 고려해야할 점은 다음과 같을 것입니다.
서버는 세션 정보를 삭제해야하고
클라이언트는 쿠키를 갱신하거나 삭제해야 합니다.
따라서 클라이언트에서 세션정보를 없애기 위해서 res.cookie로 쿠키의 값을 무효한 값으로 갱신하거나
res.clearCookie로 쿠키를 삭제해버리면 됩니다.
node.js 생태계에는 이러한 세션 관리를 대신 수행해주는 express-session이라는 모듈이 존재합니다.
🥶 쿠키와 세션의 차이점
설명 | 접속 상태 저장 경로 | 장점 | 단점 | |
쿠키 | 쿠키는 http의 stateless한 특성을 보완해주는 도구 |
클라이언트 | 서버에 부담이 덜어짐 | 쿠키 그 자체가 인증은 아님 |
세션 | 접속 상태를 서버가 가짐(stateful) 접속 상태와 권한 부여를 위해 세션 아이디를 쿠키로 전송 |
서버 | 신뢰할 수 있는 유저인지 서버에서 추가로 확인 가능함 |
하나의 서버에서만 접속상태를 가지므로 분산에 불리해짐 |
쿠키는 세션에 비해 변조가 조금 더 쉽다는 특징이 있습니다.
xss 공격으로 인해 세션 쿠키가 탈취되면 문제가 발생합니다.
🌞토큰
토큰은 세션인증방식을 보완한 인증 방식입니다.
그 중 대표적인 토큰 인증 방식으로 JWT가 있습니다. JWT는 다들 뭔지 몰라도 한번은 들어보셨을 것 같아요
이 토큰을 공부하기 이전에 먼저 알고 가면 좋은 개념들이 몇개 있으니 그것을 먼저 보도록 하겠습니다.
🧡해싱
복호화가 가능한 다른 암호화 방식들과는 달리 해싱은 암호화만 가능합니다.
그럼에도 불구하고 해싱은 가장 많이 사용되는 암호화 방식 중에 하나입니다.
이 해싱은 해시 함수를 이용하여 암호화를 진행하며 해시 함수는 다음과 같은 특징을 지닙니다.
항상 같은 길이의 문자열을 리턴한다.
서로 다른 문자열에 동일한 해시 함수를 사용하면 반드시 다른 결과값이 나온다.
동일한 문자열에 동일한 해시함수를 사용하면 항상 같은 결과값이 나온다.
이 항상 같은 인풋에 같은 아웃풋이 보장된다는 특성 때문에
해시 함수를 거치기 이전의 값을 알아낼 수 있도록 만든 표인 레인보우 테이블이 존재합니다.
따라서 이는 보안상 위협이 될 수 있기 때문에
Salt를 활용할 수 있습니다.
해싱 이전값에 임의의 값을 더해 데이터가 유출되더라도 해싱 이전의 값을 알아내기 어렵게 만드는 기법입니다.
이렇게 salt를 활용한 경우 솔트가 함께 유출된 것이 아니라면 암호화 이전의 값을 알아내기 어려워집니다.
해싱은 조금 이상한 부분이 있습니다.
복호화가 불가능하다면 왜 암호화 방식을 사용하는 것일까요?
그것은 해싱의 목적이 데이터 그 자체를 사용하는 것이 아니라 동일한 값의 데이터를 사용하고 있는지 여부만 확인하는 것이 목적이기 때문입니다.
해싱은 민감한 데이터를 다루어야 하는 상황에서 데이터 유출의 위험성은 줄이면서
데이터의 유효성을 검증하기 위해서 사용하는 단방향 암호화 방식이라고 할 수 있습니다.
토큰으로 돌아와서 토큰인증방식은 최근 웹 애플리케이션에서 많이 사용하는 인증 방식 중 하나입니다.
토큰을 사용하면 사용자의 인증 정보를 서버가 아닌 클라이언트 측에 저장할 수 있습니다.
토큰 인증 방식은 앞서 말했듯 세션 기반 인증이 가지고 있던 한계를 극복하기 위해 고안되었습니다.
토큰 인증 방식의 흐름은 다음과 같습니다.
1. 사용자가 인증 정보를 담아 서버에 로그인 요청을 보냅니다.
2. 서버는 데이터베이스에 저장된 사용자의 인증정보를 확인합니다.
3. 인증에 성공한다면 해당 사용자의 인증 및 권한 정보를 서버의 비밀키와 함께 토큰으로 암호화합니다.
4. 생성된 토큰을 클라이언트로 전달합니다.(이때 Authorization 헤더, 쿠키로 전달 등의 방법을 사용합니다.)
5. 클라이언트는 전달받은 토큰을 저장합니다.(Local Storage, Session Storage, Cookie등에 저장합니다.)
6. 클라이언트가 서버로 리소스를 요청할 때 토큰을 함께 전달합니다.(토큰을 보낼때도 Authorization헤더 , 쿠키 사용가능)
7. 서버는 전달받은 토큰을 서버의 비밀 키를 통해 검증 합니다. 이를 통해 토큰이 위조되었는지 유효기간이 지나지않았는지 확인할 수 있습니다.
8. 토큰이 유효하다면 클라이언트의 요청에 대한 응답 데이터를 전송합니다.
이러한 토큰 인증 방식은 다음과 같은 장점을 가집니다.
무상태성 : 서버가 유저의 인증 상태를 관리하지 않습니다. 서버는 비밀키를 통해 토큰유효성검증만 하면 되기 때문에 무상태적인 아키텍처를 구축할 수 있습니다.
확장성 : 다수의 서버가 공통된 세션 데이터를 가질 필요가 없다는 것도 장점입니다. 서버를 확장하기 용이해집니다
어디서나 토큰 생성 가능 : 토큰의 생성과 검증이 하나의 서버에서 이루어지지않아도 되기 때문에 토큰 생성만을 담당하는 서버를 구축할 수 있고 여러 서비스 간의 공통된 인증 서버를 구현할 수도 있습니다.
권한 부여에 용이: 토큰은 인증상태, 접근 권한 등 다양한 정보를 담을수 있기 때문에 사용자 권한 부여에 용이합니다. 이를 활용해 어드민 권한 부여 , 정보 접근 범위도 설정할 수 있습니다.
😋JWT(JSON Web Token)
토큰 기반 인증 구현 시 대표적으로 사용하는 기술로 JWT를 들 수 있습니다.
JWT 는 JSON 객체에 정보를 담고 이를 토큰으로 암호화하여 전송할 수 있는 기술입니다.
클라이언트가 서버에 요청을 보낼 때 인증정보를 암호화된 JWT 토큰으로 제공하고 서버는 이 토큰을 검증하여 인증정보를 확인합니다.
JWT는 .으로 나누어진 세부분이 존재하며 각각을 header , payload, signature라고 부릅니다.
aaaaa.bbbbb.ccccc
Header에는 마치 HTTP의 헤더처럼 해당 토큰 자체를 설명하는 데이터가 담겨있습니다.
토큰의 종류 시그니처를 만들 때 사용할 알고리즘을 JSON 형태로 작성해줍니다.
이 JSON 객체를 base64 방식으로 인코딩하면 JWT의 Header가 완성됩니다.
{
"alg" : "HS256",
"typ" : "JWT"
}
base64 방식은 디코딩을 마음만 먹으면 할 수 있는 방식이기 때문에 비밀번호와 같이
노출되어서는 안되는 민감한 정보를 담으면 안됩니다.
payload
payload는 HTTP의 페이로드처럼 전달하려는 내용물을 담고 있는 부분입니다.
어떤 정보에 접근가능한지에 대한 권한, 유저의 개인정보, 토큰의 발급 시간과 만료시간 등의 정보를 담습니다.
이 payload를 base64로 인코딩하면 Payload 가 완성됩니다.
{
"sub" : "someInformation" ,
"name" : "phillip",
"iat" : 151623391
}
이렇게요!
Signature
시그니처는 토큰의 무결성을 확인할 수 있는 부분입니다.
헤더와 페이로드가 완성되면 시그니처는 이를 서버의 비밀 키(암호화에 추가할 salt)와 Header에서 지정한 알고리즘을 사용하여 해싱합니다.
이때 salt를 추가해주기 때문에 누군가 권한을 속이기 위해 토큰의 Payload를 변조하는 등의 시도를 하더라도
토큰을 발급할 때 사용한 salt 를 정확히 알고 있는게 아니라면 유효한 시그니처를 만들 수 없기때문에
서버는 시그니처를 검증하는 단계에서 변조된 토큰을 알아낼 수 있습니다
토큰 인증 방식의 한계
시그니처를 사용해서 위조된 토큰을 알아낼 수는 있지만 토큰 자체가 탈취되면
토큰 인증 방식은 한계가 드러납니다. 그 이유는 다음과 같습니다.
1. 인증 상태를 관리하는 주체가 서버가 아니므로 토큰이 탈취되어도 해당 토큰을 만료시킬 수 없스빈다.
2. 토큰이 탈취되는 것을 대비해 유효기간을 짧게 설정하면 사용자 경험이 안좋아집니다.
3. 토큰에 여러 정보를 담을 수 있는 만큼 많은 데이터를 담으면 암호화 과정도 길어지고 토큰도 커져서 네트워크 비용이 늘어납니다.
액세스 토큰과 리프레시 토큰
Access Token
액세스 토큰은 말 그대로 서버에 접근하기 위한 토큰으로 앞서 다룬 토큰과 비슷한 역할을 합니다.
따라서 보안을 위해 보통 24시간 정도로 짧게 유효기간을 설정합니다.
Refresh Token
리프레시 토큰은 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급받기 위해 사용되는 토큰입니다.
따라서 리프레시 토큰은 액세스 토큰보다 긴 유효기간을 설정합니다.
🤢OAuth
OAuth란 인증을 중개해주는 메커니즘을 말합니다.
보안된 리소스에 액세스하기 위해 클라이언트에게 권한을 제공하는 프로세스를
단순화 시켜주는 프로토콜입니다.
OAuth란 다른 웹사이트 상의 자신들의 정보에 대해 접근권한을 부여할 수 있는
공통적인 수단 / 개방형 표준입니다.
이미 사용자 정보를 가지고 있는 웹서비스(naver,kakao,google)에서
사용자의 인증을 대신해주고 접근 권한에 대한 토큰을 발급한 후 내 서버에서 인증을 가능하게 합니다.
이러한 OAuth는 서비스를 구현하는 개발자 입장에서 보았을 때에도
신규 회원가입이나 회원 관리를 신경 쓰지 않아도 되기 때문에 선호되고 있습니다.
뿐만 아니라 OAuth는 보안상의 이점도 존재하는데 검증되지 않은 APP에서 OAuth를 이용해 로그인 한다면
유저의 민감한 정보가 직접 App에 노출될 일이 없고 인증 권한에 대한 허가를
미리 유저에게 구해야하기 때문에 더 안전하게 사용할 수 있습니다.
이러한 OAuth는 어떤 메커니즘으로 동작하게 될까요?
OAuth는 다음과 같은 주체를 가집니다.
사용자 | OAuth 인증을 통해 소셜 로그인을 하고자 하는 사용자 |
리소스 서버 | 사용자가 소셜 로그인을 하기 위해 사용하는 사용자의 정보를 저장하고 있는 서버 |
새로운 서비스 | 사용자가 소셜 로그인을 활용해 이용하고자 하는 새로운 서비스 |
이렇게 보니 당연히 있어야 할 애들만 있는 느낌이 드네요
좀 더 자세하게 살펴보면 다음과 같습니다.
사용자 (Resource Owner)
리소스는 사용자의 이름이나 전화번호 등을 뜻합니다. 이러한 정보의 주인이 사용자이기 때문에
리소스 오우너라고 부릅니다. 멋지네요
서버 (Resource Server & Authorization Server)
Resource Server는 사용자의 정보를 저장하고 있는 서버를 뜻합니다.
Authorization Server는 인증을 담당하는 서버를 특정해서 부를때 사용합니다.
서비스(Application)
사용자가 소셜 로그인을 활용해 이용하고자 하는 새로운 서비스를 뜻합니다.
경우에 따라서는 애플리케이션을 클라이언트와 서버로 세분화해서 지칭하기도 합니다.
이러한 OAuth 인증방식의 종류와 흐름은 다음과 같습니다.
먼저 종류부터 보면 이정도로 정리할 수 있습니다.
Implicit Grant Type | Authorization Server에서 Access Token을 받아오는 방식 |
Authorization Code Grant Type | Authorization Code를 사용한 인증단계가 추가로 있어 비교적 안전합니다. |
Refresh Token Grant Type | Authorization Server에서 리프레시 토큰을 검증한 뒤 액세스 토큰을 다시 발급 |
Authorization Code Grant Type 방식은 OAuth 2.0 에서 사용하는 방식입니다.
유효한 인증 요청인지 확인한 후 Authorization Code를 발급하고
이 코드를 애플리케이션에 전달한 뒤 애플리케이션이 Authorization Code를 다시
Authorization Server에 전달한 뒤에 액세스 토큰을 전달받는 식의
인증단계가 추가로 있기 때문에 비교적 안전하다는 특징이 있습니다.
이러한 OAuth 방식은
쉽고 안전하게 새로운 서비스를 이용할 수 있다는 장점과
권한 영역을 설정할 수 있다는 장점이 존재합니다.
🐶마치며
겉핥기 수준으로만 개념들을 흝어본것같은데도
분량이 장난이 아니네요 살려줘...
'코드스테이츠 프론트' 카테고리의 다른 글
코드스테이츠 프론트엔드 44기 부트캠프 수료 후기 SEB FE 44 (5) | 2023.09.02 |
---|---|
리액트 리덕스를 간단하게 정리해보자 (0) | 2023.04.24 |
UI/UX (1) | 2023.04.13 |
반딧불반 - [S2U10 Web Server 기초] (0) | 2023.04.06 |