recordingbetter's devlog

Python, Django, DRF, Postgresql, AWS, Docker....

토큰 기반의 유저 인증 시스템

07 August 2017


토큰 기반의 유저 인증 시스템

  • 쿠키와 세션을 사용하지 않고 토큰을 기반하여 유저를 인정하는 방식

기존 유저 인증 시스템 과정

  1. 웹 어플리케이션에서 username, password를 받아 서버로 요청을 보낸다.
  2. 서버는 DB를 쿼리하여 user를 검증한다. user가 유효하면 세션을 생성하고 세션 정보를 Response 헤더에 포함시켜 반환한다.
  3. 클라이언트(웹브라우저)는 세션 정보를 쿠키에 저장하고 유저 인증이 필요한 정보에 유저가 접근할때 모든 Request 헤더에 세션 정보를 포함시킨다.
  4. Request 헤더에 포함된 세션 정보가 유효하면 서버는 유저가 접근한 정보를 렌더링된 HTML로 반환한다.

기존 유저 인증 시스템의 한계

  1. 모바일 어플리케이션에서는 서버에서 생성된 세션과 쿠키를 공유할 수 없다.(쿠키 컨테이너를 사용하면 된다고 하는데 번거롭다고 함)
  2. 모바일 클라이언트에서는 HTML외에 JSON, XML같은 포멧의 응답이 필요하다.

토큰 기반의 유저 인증 시스템 과정 (로그인)

  1. 웹 어플리케이션에서 username, password를 받아 서버로 요청을 보낸다.

  2. 서버는 DB를 쿼리하여 user를 검증한다. user가 유효하면 토큰을 생성하고 DB(Token 테이블)에 저장한 뒤, Response 헤더에 포함시켜 반환한다.

  3. 클라이언트(웹브라우저)는 유저 인증이 필요한 정보에 유저가 접근할때 모든 Request 헤더에 토큰 정보를 포함시킨다.
  4. Request 헤더에 포함된 세션 정보가 유효하면 서버는 유저가 접근한 정보를 JSON 또는 XML 포멧으로 반환한다. (우리는 JSON)
  5. 로그아웃을 하면 DB(Token 테이블)에 저장되어있던 토큰을 삭제한다.

토큰 기반 인증의 장점

  1. 토큰 기반 인증에서 토큰은 요청 헤더를 통해 전달된다. 이것은 stateless를 의미한다. HTTP 요청을 만들 수 있는 클라이언트라면 누구든지 서버로 요청을 보낼 수 있다.

  2. 대부분의 현재 웹 어플리캐이션 내에서 view는 백엔드 상에서 렌더링되고 브라우저로 HTML이 반환된다. 프론트엔드의 로직이 백엔드 코드와 의존성이 있는 것이다. 이렇게 의존성이 생기면 몇가지 문제가 발생한다. 예를 들어, 프론트엔드 HTML, CSS, JS 등을 구현하는 디자인 에이전시와 함께 일을 한다고 가정해보자. 우리는 일부 렌더링 또는 생성 동작을 수행하기 위해 프론트엔드 코드를 가져와서 백엔드 코드에 통합시켜야 한다. 어쩌면 렌더링된 HTML 컨텐츠는 디자인 에이전시가 구현했던 것과 아주 많이 다를 것이다. 토큰 기반 인증에서는 프론트엔드 프로젝트를 백엔드로부터 독립적으로 개발하도록 할 수 있다. 백엔드 코드는 렌더링된 HTML 대신 JSON 응답을 반환할 것이고, 경량화되고 압축된 버전의 프론트엔드 코드는 CDN에 넣어둘 수가 있다. 누군가 웹페이지에 방문하면 HTML 컨텐츠는 CDN에서 제공되고 페이지의 내용은 인증 헤더의 토큰을 사용하는 API 서비스에 의해 생성될 것이다.
  3. No Cookie-Session (or NO CSRF). CSRF(사이트간 요청 위조)는 세션 유지에 일반적으로 사용되는 쿠키 정보만 만족하면 요청이 수행되는 취약점을 이용한 공격이다. 예를 들어, 이미 사이트에 로그인하여 쿠키를 들고 있는 사용자가 공격자가 유도한 링크(회원 탈퇴 링크)에 노출되면 회원 탈퇴 요청이 서버로 전달되고 탈퇴가 되어버린다. 이미 쿠키가 사용자 정보를 포함하고 있기 때문에 회원 탈퇴 요청 URL로 접속만하면 웹서버는 요청을 신뢰하고 명령을 수행하는 것이다. 이 문제를 해결하기 위해 탈퇴 시 비밀번호를 한번 더 요구하거나 요청에 토큰과 같은 credential을 포함하는 방법을 사용한다. 토큰 기반 인증에서 토큰은 인증 헤더 내에 포함되기 때문에 CSRF를 방지할 수 있다.
  4. 어플리캐이션 내에서 세션 읽기, 쓰기, 삭제 동작이 발생하면 최소 1회 OS의 temp 폴더에 file 관련 동작이 발생한다. 여러개의 서버를 갖고 있고 한 세션이 첫번째 서버에 생성되었다고 해보자. 이 상태에서 새로운 요청이 발생하고 그 요청이 다른 서버에 전달되면, 해당 서버에는 세션 정보가 없을 것이기 때문에 “unauthrized” 응답을 받을 것이다. 물론 sticky 세션(처음에 접속했던 서버와 같은 서버에 계속 연결시키는 것)을 사용하여 이 문제를 해결할 수 있다. 하지만 토큰 기반 인증에서는 이 문제 자연스럽게 해결된다. 요청 토큰은 모든 요청, 모든 서버가 가로채기 때문이다.
  5. 확정성 : 여기서의 확장성은, Scalability 와는 또 다른 개념입니다. Scalability 는 서버를 확장하는걸 의미하는 반면, Extensibility 는 로그인 정보가 사용되는 분야를 확장하는것을 의미합니다. 토큰을 사용하여 다른 서비스에서도 권한을 공유 할 수 있습니다. 예를 들어서, 스타트업 구인구직 웹서비스인 로켓펀치에서는 Facebook, LinkedIn, GitHub, Google 계정으로 로그인을 할 수 있습니다. 토큰 기반 시스템에서는, 토큰에 선택적인 권한만 부여하여 발급을 할 수 있습니다 (예를들어서 로켓펀치에서 페이스북 계정으로 로그인을 했다면, 프로필 정보를 가져오는 권한은 있어도, 포스트를 작성 할 수 있는 권한은 없죠)

code

  • settings.py에 아래 내용 추가

# token 인증을 위한 인증자
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    )
}

# rest_framework에 포함되어있음. 설치 필요없음.
INSTALLED_APPS = [
    'rest_framework.authtoken',
]

이후 필히 migrate 해야함. DB에 “token” 테이블이 생성됨.

  • urls.py에 아래 내용 추가

# 유저 인증과 토큰 생성을 위한 내장 view. username과 password 값을 가지고 요청하면 됨.
# 인증되면 토큰을 반환한다.
from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [
    url(r'^login/$', obtain_auth_token),
]
  • user models.py에 아래 내용 추가

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

# post_save 시그널을 받아 토큰을 생성한다.
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)
  1. postman에서 username, password 값을 가지고 “~/login/” 주소로 “POST” 요청을 보낸다.
  2. user가 인증되면 token 값이 반환된다.
  3. user 생성 시에도 token 값이 반환된다.
blog comments powered by Disqus