유저 기능 원리 (1)

깨진알·2024년 2월 25일

More

목록 보기
4/5

인증과 인가

1. 쿠키 인증

쿠키란, 서버 response나, 클라이언트 코드에 따라 브라우저에 저장되는 작은 단위의 문자열 파일들을 의미한다.

쿠키 저장과 전송 기능은 브라우저가 자동으로 실행해 주기 때문에 조금 더 편리하게 인증 구현이 가능하다.


2. 쿠키 보안

쿠키는 유저 인증뿐만 아니라, 브라우저 이용자에 대한 개인화된 기능과 데이터 제공 수단으로 사용할 수 있다. 로그인을 하지 않아도 검색 기록이 저장되거나, 쇼핑 카트를 사용할 수 있다거나, 한 번 설정한 라이트와 다크 테마도 유지되는 경우들이 쿠키를 통해 이루어지는 것들이다.

하지만 브라우저 사용자가 아닌 다른 사람이 쿠키를 가로채거나 여러 방법들로 악용한다면, 보안 문제가 생길 수 있다. 특히 인증 관련 쿠키가 악용된다면 큰 문제로 이어질 수 있다.

(response 쿠키 예시)

(1) Secure

서버에서 response를 보낼 때 이 설정을 추가해주면 HTTP보다 보안에 강한 HTTPS를 사용할 때만 클라이언트에서 서버로 쿠키가 보내진다. HTTPS를 사용하면 항상 request와 response가 암호화되기 때문에 누군가 중간에 request를 가로챘을 때 정보 유출을 줄일 수 있다.

Secure 설정은 response Set-Cookie 헤더에 이름-값 쌍 뒤 ;Secure 키워드를 사용해서 적용할 수 있다.

Set-Cookie: cookie_name=cookie_value; Secure;

(2) HttpOnly

이 설정을 추가하면 클라이언트가 자바스크립트 코드로 해당 쿠키에 접근할 수 없게 된다. 자바스크립트 코드로는 가지고 올 수 없고, 그냥 쿠키를 설정한 웹 사이트에 request로 보낼 수만 있는 것이다. 코드로 쿠키에 접근할 수 없으면, 악의적 클라이언트가 개인 정보에 직접 접근하는 걸 막을 수 있다. 하지만 때에 따라서 코드로 저장한 쿠키에 접근하고 싶을 수 있으니까, 필요에 따라 설정하면 된다.

HttpOnly 설정은 response Set-Cookie 헤더에 이름-값 쌍 뒤 ;HttpOnly 키워드를 사용해서 적용할 수 있다.

Set-Cookie: cookie_name=cookie_value; Secure; HttpOnly;

(3) SameSite

이 설정은 Cross Site request forgery의 약자, CSRF(XSRF)라는 공격을 예방할 수 있는 설정이다. CSRF는 일반 사이트 A와 악의적 사이트 B가 있을 때, B 웹 페이지에서 브라우저에 저장된 쿠키를 가지고 사이트 A 서버로 request를 보내는 공격이다

SameSite를 Strict로 하면 다른 도메인에서 request를 보낼 때 쿠키가 가는 걸 아예 방지할 수 있다. 이해하기 쉽게 말하자면 request를 보내는 클라이언트와 이걸 받는 서버의 도메인이 서로 같을 때만 쿠키가 간다.

하지만 URL을 직접 쳐서 사이트를 방문하기도 하지만, 이메일이나, 메세지, 심지어 다른 페이지에 있는 링크를 통해서도 페이지를 방문한다. 이때도 쿠키가 가지 않는 문제가 생긴다. 그래서 URL에 직접 링크를 치면 저장되어있던 쿠키가 가지만, 친구가 메세지로 보낸 링크를 통해서 페이지를 방문할 때는 쿠키가 가지 않아서 다시 로그인을 해야 되는 것이다. 물론 보안을 위해서 이렇게 되길 원하면 상관없지만, 인증 정보처럼 민감한 정보가 아닐 때는 관한 조치일 수 있다.

이때 사용하는 설정이 Lax이다. Lax는 영어로 '느슨한'이라는 뜻이다. 이 설정을 하면 링크를 통해 사이트를 직접 방문할 때는 쿠키가 보내진다.

SameSite 설정을 None으로 하면 아무런 제한 없이 브라우저에서 보내는 모든 request에 쿠키가 붙어서 간다. SameSite 설정을 None으로 할 때에는 보안 문제 때문에 항상 Secure 설정을 추가해야 한다. 추가하지 않으면 특정 브라우저들은 보안이 취약하다고 판단하고 쿠키를 저장하지 않는다.

SameSite 설정은 response Set-Cookie 헤더에 이름-값 쌍 뒤 ;SameSite 키워드, 그리고 원하는 쿠키 사용 범위 키워드, None, Lax, Strict를 사용해서 적용할 수 있다.

Set-Cookie: cookie_name=cookie_value; Secure; HttpOnly; SameSite=Lax;

3. Authorization 헤더 인증

  • 장점
    request에 인증서를 붙일지 안 붙일지 선택 가능하다. 또한 서로 다른 루트 도메인 사이에서 인증이 가능하다.

  • 유의해야될 점
    비밀번호와 같이 민감한 정보는 쿠키나 로컬 스토리지에 절대 저장하지 않는다.


4. 세션 기반 인증

세션이란, 서버가 저장하는 사이트 방문자들에 대한 기록을 의미한다.


5. 토큰 기반 인증

인증 토큰이란, 유저에 대한 정보를 암호화한 문자열을 의미한다. 접근 토큰이라고 부르기도 한다.

최근에는 JWT(JSON Web Token) 형식을 많이 사용한다.


6. 인코딩과 Base64URL

(1) 인코딩

HTTP 헤더 이름과 값들은 효율성과 안정성을 위해 미리 정해진 256개의 문자들만 사용해야 한다. 이 문자들은 ASII라는 표준에 포함된 문자들이다. 이 표준을 따르면 실제로는 0과 1로 저장되는 데이터가 여러 컴퓨터에서 다르게 해석되는 걸 방지할 수 있다.

근데 세상에는 ASCII에 포함되지 않는 문자들도 많이 있다. 특히 인증 관련 데이터처럼 유저가 직접 정한 값들은 정해진 256개의 문자들만 사용한다고 가정할 수 없다. 이런 걸 헤더 값으로 사용하고 싶을 때는 직접 ASCII에 포함된 문자로 바꿔서 사용해야 한다.

데이터를 여러 곳에서 쉽고 안정적이게 사용하기 위해 통일된 형식으로 바꾸는 걸 '인코딩'이라고 부른다. 읽는 사람이 무슨 말인지 알 수 없게 만드는 '암호화'와는 조금 다른 개념이다. 여러 인코딩 방식들이 있지만 웹에서는 base64url이 많이 사용된다.

(2) Base64 인코딩

Base64는 데이터를 0과 1로 표현하고 이걸 6자리씩 끊어서 ASCII에 포함된 64문자 중 하나로 바꿔주는 인코딩 방식이다. 예를 들어 000000A, 101000o 이런 식으로 000000부터 111111까지 각 숫자에 해당하는 문자로 바꾸는 것이다. 참고로 ASCII에는 256개의 문자가 있지만, 이 중 64개로만 사용한다. 64개의 문자열은 모든 영문 대소문자, 모든 숫자, 그리고 +/로 이루어져 있다. 자릿수가 부족할 때는 = 문자로 채워넣는다.

À È Ì Ò Ù Ỳ Ǹ Ẁ <=> w4Agw4ggw4wgw5Igw5kg4buyIMe4IOG6gA==
한글도 인코딩할 수 있어요 <=> 7ZWc6riA64-EIOyduOy9lOuUqe2VoCDsiJgg7J6I7Ja07JqU

반대로 오른쪽에 데이터가 있고 이게 base64 방식으로 인코딩됐다는 걸 알면 누구나 손쉽고 빠르게 원래의 문자열로 바꿀 수 있다. 인코딩된 데이터를 원래 형태로 바꾸는 걸 디코딩이라고 한다.

(3) Base64URL 인코딩

Base64URL은 Base64와 거의 똑같은 인코딩 방식이다. +/ 문자들은 URL에 사용될 때 특정 의미를 갖기 때문에 이 두 문자 대신 의미가 없는 -_를 사용한다. 웹에서 두 문자에 의미가 더 부여돼 해석될 가능성을 줄여서 조금 더 안정하게 사용할 수 있다.


7. 기본 인증(Basic Authentication)

(1) 기본 인증

세션과 토큰 기반 인증, 이 두 인증 방식은 문자열 형식의 '인증서'같은 개념을 사용하는 것인데, 사실 인증을 할 때는 '인증서'의 개념을 아예 사용하지 않아도 되고, 그냥 온전히 이메일과 비밀번호만 사용할 수도 있다. 바로 기본 인증(Basic Authentication)이라는 방식을 사용하면 된다.

기본 인증의 request의 Authorization 헤더를 사용한다. 토큰 인증을 할 때 Authorization 헤더 뒤에 Bearer 또는 Token 그리고 뒤에 토큰을 붙였던 것과 비슷하게 Authorization 헤더 뒤에 Basic, 그리고 이메일과 비밀번호를 :로 이어서 붙여주면 된다. (Authorization: Basic email:password)

참고로 토큰 기반 인증과 기본 인증에서 Bearer/Token 또는 Basic을 붙여주는 건 서버에서 뒤에 붙이는 인증 정보의 종류를 서버에 알려주기 위한 방법이다. Authorization 헤더 인증을 사용한다면 꼭 추가해야 한다.

username:password 이 부분은 서버로 보내기 전에 base64url 인코딩한다. 이 후 인코딩 결과로 나온 문자열을 이용해 Authorization: Basic "문자열" 형태로 작성한다. 이렇게 한 후 request를 보내면, 서버가 Baisc을 통해서 뒤에 있는 정보가 기본 인증이란 걸 파악하고, 이걸 디코딩해서 유저를 인증한다.

(2) 기본 인증의 단점

기본 인증은 유저를 인증하는 자체에 있어서는 보족함이 없지만, 보안 문제로 인해 요즘에는 거의 사용하지 않는다.

이메일과 비밀번호를 누군가에게 노출됐을 때 세션 id나 토큰보다 훨씬 더 악용할 수 있는 여지가 많다. 모든 request에 이런 민감한 데이터를 보내면 악의를 갖는 공격자가 중간에서 가로챌 위험이 커진다. 또, 로그인 상태를 유지하기 위해서는 클라이언트가 이메일과 비밀번호를 어딘가에 저장해놓고, 가지고 와서 request에 부붙여야 되는데, 이렇게 민감한 정보를 브라우저에 저장해놓는 것도 마찬가지로 노출될 위험이 있기 때문에 안전하지 않다.


8. Refresh 토큰

Access 토큰은 갖고 있는 유저에게 특정 권한을 주기 위한 목적으로 사용한다. 사실 아무런 안전장치 없이 단독으로 사용하면 안정성 문제가 생길 수도 있다. 특히 토큰의 만료 기간을 길게 잡으면 토큰을 누군가 가로챘을 때 더 오랫동안 특정 권한을 갖는 유저 행세를 할 수 있다. 그렇다고 해서 만료 기간을 짧게 잡으면 이메일과 비밀번호로 인증을 너무 자주 해야해서 귀찮고 위험해질 수 있다. 이 문제를 어느 정도 해소하기 위해서 때때로 refresh 토큰이란 걸 같이 사용한다.

refresh 토큰은 access 토큰이 만료됐을 때, 이메일 비밀번호를 사용하지 않고 access 토큰을 새롭게 발급받는데 사용되는 토큰이다. 먼저 유저가 로그인을 하기 위해 request로 서버에 이메일과 비밀번호를 보내고, 서버가 이걸 확인하면, 서버는 클라이언트에서 access와 refresh, 두 가지의 토큰을 보내줄 수 있다.

Access 토큰이 소유자가 특정 권한을 가질 수 있게 하는 토큰이라면, refresh 토큰은 이메일과 비밀번호를 사용하지 않고 새로운 access 토큰을 발급받을 수 있게 하는 토큰이다. Access 토큰을 사용하다가 만료가 돼서 더 이상 request를 인증할 수 없게 되는 경우일 때, 클라이언트는 access 토큰을 새롭게 발급받는 URL에 새로운 GET request를 보낸다. 이 때 body에 refresh 토큰을 함께 보내게 되는 것이다. 그럼 서버는 refresh 토큰이 유요한 걸 확인한 후, 새로운 access 토큰을 발급한 후, response로 클라이언트에게 돌려준다.

일반적으로 refresh 토큰이 access 토큰보다 만료 기간이 더 길다. access 토큰의 만료 기간이 짧기 때문에 서버로 토큰을 보낼 때 누군가 이 정보를 가로챈다고 해도 최대 10분 정도까지만 다른 유저 행세를 할 수 있다.

refresh 토큰은 access 토큰이 만료됐을 때만 보내면 되기 때문에 훨씬 더 적게 보내도 되고 그만큼 누가 빼돌릴 확률이 줄어든다. 클라이언트가 이 두 토큰을 저장하고 있으면, refresh 토큰이 만료할 때까지 특정 권한을 갖는다는 걸 증명할 수 있는 것이다. 그렇기 때문에 이메일 비밀번호를 받지 않아도 계속 로그인 상태를 유지할 수 있어 편리하고, 이메일과 비밀번호라는 개인 정보를 request로 최대한 적은 횟수로 보내도 되기 때문에 더 위험부담을 줄일 수 있다.


9. JWT(JSON Web Token)

JWT는 JSON 형식의 데이터를 문자열로 인코딩한 토큰을 의미한다.

  • Header
    토큰 자체에 대한 데이터를 저장한다.

  • PayLoad
    토큰이 실질적으로 저장하려는 정보가 담겨있다. 저장하고 싶은 데이터의 종류에는 제한이 없다. 다만, 최대한 짧게 작성하는 것이 좋다. 여기에는 공식적으로 사용하는 이름이 있기 때문에 최대한 활용하는 것을 추천한다. (exp, iat, jti 등)

  • Signature
    토큰을 믿을 수 있는지 확인하기 위한 데이터를 저장한다. (Header + PayLoad + 시크릿 키)

단, Header와 PayLoad 부분은 단순히 인코딩된 것이기 때문에 아무나 볼 수 있다는 점도 주의해서 사용해야 한다.


10. 세션 vs 토큰 기반 인증

(1) 효율성

토큰 기반 인증의 첫 번째 장점은 효율성이다. 세션 기반 인증을 사용하면 서버는 항상 로그인 세션 정보를 저장하며, 매 request의 유저가 누구인지를 비교해야 한다. 그리고 이걸 하기 위해서는 용량과 시간이 소비된다. 로그인한 유저가 엄청 많거나 특정 시간에 몰리게 되면 서버의 request 처리 속도가 느려질 수 있다. 반면 토큰 기반 인증은 어딘가 저장한 데이터와 비교하는 게 아니라 토큰 자체 내용을 해석하기만 하면 되기 때문에 더 효율적으로 작동할 수 있다.

(2) 유연성

토큰 기반 인증은 세션 기반 인증보다 조금 더 유연하게 사용될 수 있다. 토큰을 발행하는 방법이 똑같고, 시크릿 키만 있으면, 발행을 한 곳과 확인을 하는 곳이 달라도 된다.

예를 들어 같은 유저 데이터베이스를 사용하는 여러 서비스들이 있고, 이 사이트들이 같은 방식과 키를 사용해서 토큰을 발행한다면, 한 사이트에서 제공한 토큰을 가지고 있으면, 다른 서비스가 그걸 해석해서 유저를 파악할 수 있다. 요즘은 크고 복잡한 웹 애플리케이션들을 더 작은 내용을 담당하는 작은 부분들로 나눠서 개발하는 경우 많기 때문에, 토클을 사용하는 게 더 유연하다.

(3) RESTful API

세션 정보와 같이 서버가 "상태" 정보를, 예를 들어 유저가 로그인을 했는지 안 했는지 저장하고 있을 때, stateful하다고 표현한다. REST에 부합하기 위해서는 서버가 상태 정보를 저장하지 않는, stateless한 특성이 있어야 한다. 서버는 클라이언트에서 보내는 정보만으로 충분히 상태를 파악할 수 있어야 한다. 이 기준에서 살펴보면 RESTful한 API 서버를 만들고 있다면 세션 기반 인증보다 토큰 기반 인증이 더 어울린다.

(4) 무효화

세션 기반 인증의 장점 중 하나는 서버에서 세션 데이터를 따로 관리를 하기 때문에 특정 세션을 손쉽게 무효화할 수 있다는 점이다. 그냥 세션을 관리해서 상태를 만료로 바꾸거나 마료일을 지금 당장으로 해버리면 된다. 하지만 토큰 기반 인증을 사용하면 따로 서버가 상태 정보를 저장하지 않기 때문에 특정 토큰을 무효화하는게 더 복잡해진다. 그리고 이건 꽤 큰 문제들로 이어질 수 있다. 예를 들어 금융 서비스에서 누군가 유저 토큰을 가로챘는데 이걸 바로 무효화할 수 없다면 돈을 뺏기는 문제가 생길 수도 있다. 물론 이중 비밀번호나, OTP라든지 이걸 방지하기 위한 방법들이 있지만, 세션 기반 인증을 사용하면 한 가지 안전장치를 더 손쉽게 사용할 수 있다.


11. 인가 : Authorization


12. Authorization 헤더를 인증에 사용하는 이유

request를 보낸 유저가 "누군인지" 파악하는 인증과 request가 "특정 작업을 요청할 권한이 있는지" 파악하는 인가는 서로 다른 두 기능이다. 하지만 request의 Authorization(인가) 헤더를 이용하는 유저 인증 방법을 이런 식으로 사용했다.

DELETE http://www.example.com/api/some_resource/1/
Authorization: Bearer <token>

공식 문서에 따르면 Authorization은 인가가 아닌 인증 데이터를 보내기 위한 헤더라고 정의되어 있다. 공식 문서는 약 20년 전 작성된 것으로, 유저 기능의 두 용어를 구별해서 사용하는 요즘 기준으로 생각하면 이름을 잘 못 지었다고 할 수 있다. 하지만 왜 헷갈리는 이름 실수를 바로 잡지 않는것인가?

이미 Authorization 헤더를 너무 널리 사용하기 때문에 이름을 바꾸면 많은 것들을 망가트릴 수 있거나, 두 개의 기준이 섞여 사용돼서 혼란을 일으킬 수 있기 때문이라고 생각한다. 이유가 어찌 됐던, Authorization 헤더는 인가가 아닌 인증을 위한 헤더라는 점을 이해하고 사용하면 된다.

profile
프론트엔드 지식으로 가득찰 때까지

0개의 댓글