Code


Version 3 (modified by spike, 6 years ago) (diff)

--

장고의 사용자 인증

장고는 사용자 인증 시스템을 가지고 있습니다. 사용자 계정, 그룹, 허용권한(permission)과 쿠키를 기반으로 한 세션(session)을 지원합니다.

개요

인증 시스템은 다음으로 구성되어 있습니다.:

  • 사용자
  • 허용권한(permission): 작업 단위로 허용 여부를 바이너리(binary)로 구분
  • 그룹: 하나 이상의 사용자에게 label과 허용권한(permission)을 부여
  • 메세지: 사용자에게 메세지를 전달

설치

인증은 장고의 django.contrib.auth에 포함되어 있습니다. 설치는 다음을 따르세요.:

  1. 'django.contrib.auth'을 여러분의 settings.py의 INSTALLED_APPS에 추가하세요.
  2. manage.py syncdb을 실행하세요.

참고로 'django.contrib.auth'django-admin.py startproject로 자동으로 만들어지는 settings.pyINSTALLED_APPS 항목에 포함되어 있습니다. INSTALLED_APPS'django.contrib.auth'가 이미 포함되어 있다면 manage.py syncdb로 몇번이든 새로 인증시스템을 설치할 수 있습니다.

syncdb 명령은 설치된 어플리케이션(installed apps)에 필요한 데이터베이스 테이블을 새로 만들고 처음 실행하는 경우에 관리자 계정을 추가로 만듭니다.

위 과정은 한번으로 충분합니다.

사용자

사용자는 django/contrib/auth/models.py에 구현된 장고 모델(Django model)로 표현됩니다.

API 참고

필드(field)

User 객체(object)는 다음과 같은 필드(field)를 가지고 있습니다.:

  • username - 반드시 필요. 최대 30자까지. 반드시 문자나 숫자, '.'이나 '_' first_name - 추가 선택. 최대 30자까지.
  • last_name - 추가 선택. 최대 30자까지.
  • email - 추가 선택. 이메일 주소.
  • password - 반드시 필요. 비밀번호를 해쉬(hash)로 저장합니다. 장고는 비밀번호를 읽을 수 있도록(raw password) 저장하지 않습니다. 비밀번호는 무한정 길어질 수 있고 어떤 글자도 포함될 수 있습니다. 아래 "Passwords" 항목을 읽어보세요.
  • is_staff - Boolean. 관리자 사이트에 접근할 수 있는 여부
  • is_active - Boolean. 로그인할 수 있는 여부. 사용자를 제거하는 대신 이 항목을 False로 설정하세요.
  • is_superuser - Boolean. 모든 허용권한(permission)을 가지도록 합니다.
  • last_login - 사용자가 마지막으로 로그인할 날짜와 시간(datetime). 기본으로 현재 시간(date/time)이 저장됩니다.
  • date_joined - 사용자가 만들어진 날짜와 시간(datetime). 기본으로 현재 시간(date/time)이 저장됩니다.

메소드(method)

User 객체(object)는 두 개의 many-to-many field를 가집니다.: groupsuser_permissions. User 객체(object)는 다른 Django model?과 같은 방법으로 연관된 객체(object)에 접근할 수 있습니다.:

myuser.groups = [group_list]
myuser.groups.add(group, group,...)
myuser.groups.remove(group, group,...)
myuser.groups.clear()
myuser.user_permissions = [permission_list]
myuser.user_permissions.add(permission, permission, ...)
myuser.user_permissions.remove(permission, permission, ...]
myuser.user_permissions.clear()

미리 정의된 이러한 메소드(method)와 함께, User 객체(object)는 다음과 같은 특별한 메소드(method)를 가지고 있습니다.

  • is_anonymous() - 항상 False를 반환합니다. 이것으로 UserAnonymousUser를 구분할 수 있습니다. 일반적으로 is_authenticated()을 사용하는 것이 유리합니다.
  • is_authenticated() - 항상 True를 반환합니다. 사용자가 인증을 통과했는지 말해줍니다. 단, 이 메소드(method)는 사용자의 허용권한(permission)이 아니라 사용자가 정확한 usernamepassword로 인증을 통과했는지만 판단합니다.
  • get_full_name() - first_namelast_name를 빈칸(space)으로 묶어서 가져옵니다.
  • set_password(raw_password) - 사용자 비밀번호를 지정합니다. User객체(object)에 직접 비밀번호를 저장하지 마세요.
  • check_password(raw_password) - 주어진 사용자 비밀번호가 정확한지 검사합니다.
  • set_unusable_password() - 장고 개발버전에 새롭게 추가 비밀번호를 사용하지 않도록 합니다. 단, 이것은 password 필드(field)를 빈공백(blank string)으로 처리하는 것과는 다릅니다. password 필드(field)에 빈 공백(blank string)으로 저장하면 check_passwordTrue를 반환하지 않습니다. User객체(object)에 직접 저장하지 마세요.

이 방법은 LDAP 디렉토리 서비스와 같이 외부 인증시스템을 사용할 경우에 사용하세요.

  • has_usable_password() - 장고 개발버전에 새롭게 추가 set_unusable_password()가 설정된 사용자는 False를 반환합니다.
  • get_group_permissions() - 사용자가 속한 그룹의 허용권한(permission) 리스트(list)를 반환합니다.
  • get_all_permissions() - 사용자과 사용자의 그룹의 허용권한(permission) 리스트(list)를 반환합니다.
  • has_perm(perm) - 사용자가 "package.codename"에 명시된 특정한 허용권한(permission)을 가지고 있는지 검사합니다. is_activeFalse인 경우 항상 False를 반환합니다.
  • has_perms(perm_list) - "package.codename"에 명시된 허용권한(permission)들을 가지고 있는지 검사합니다. is_activeFalse인 경우 항상 False를 반환합니다.
  • has_module_perms(package_name) - 사용자가 특정한 장고 패키지(package)과 관련된 허용권한(permission)을 가지고 있는지 검사합니다. is_activeFalse인 경우 항상 False를 반환합니다.
  • get_and_delete_messages() - 사용자에게 전달될 Message 객체(object) 리스트(list)를 큐(queue)에서 가져오고, 가져온 다음 큐(queue)에서 삭제합니다.
  • email_user(subject, message, from_email=None) - 사용자에게 메일 메세지를 보냅니다. from_email항목이 None일 경우에는 DEFAULT_FROM_EMAIL? 설정이 사용됩니다.
  • get_profile() - 사용자의 사이트 프로필(site-specifi profile)을 가져옵니다. 사이트의 프로필이 없을 때는 django.contrib.auth.models.SiteProfileNotAvailable 예외(Exception)가 발생합니다.

관리 함수(function)들

User모델(model)은 유용한 함수(function)들을 가지고 있습니다.

  • create_user(username, email, password=None) - 사용자를 만들고 User객체(object)를 반환합니다. 만들어진 사용자는 is_active=True로 지정됩니다.

비밀번호가 주어지지 않는 경우에 set_unusable_password() 메소드(method)가 호출됩니다.

사용자 만들기에서 예제를 참고하세요.

  • make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789') 주어진 길이와 글자들로 임의의 비밀번호를 만듭니다. (참고로 기본으로 지정된 allowed_chars에는 1, I and 0와 같은 햇갈리는 글자들은 제외되었습니다.)

기본적인 사용법

사용자 만들기

일반적으로 create_user 함수(function)를 사용해서 사용자를 만듭니다.:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

 # 여기서 사용자 객체(object)는 이미 데이터베이스에
 # 저장되었습니다. 객체(object)의 속성(attribute)을 변경할 수
 # 있습니다.
>>> user.is_staff = True
>>> user.save()

비밀번호 바꾸기

set_password()로 비밀번호를 변경합니다.:

>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username__exact='john')
>>> u.set_password('새로운 비밀번호')
>>> u.save()

특별한 이유가 없다면, 비밀번호를 User객체(object)의 password 속성(attribute)으로 지정하는 방법으로 변경하는 것은 바람직하지 않습니다. 이유는 다음 섹션에서 설명합니다.

비밀번호

User객체(object)의 password속성(attribute)은 다음과 같이 구성됩니다.:

hashtype$salt$hash

달러표시(dollar-sign)로 구분된 hashtype, salt 그리고 hash의 조합입니다.

Hashtype은 sha1 (기본), md5 혹은 crypt 중 하나입니다. -- 비밀번호를 거꾸로 풀 수 없는 알고리즘입니다. Salt는 hash를 만드는 비밀번호를 처리하는 임의의 글자(random string)들입니다. 참고로 crypt 방법은 crypt 모듈을 포함하고 있는 표준 Python이 지원하는 플랫폼에서만 지원되고, crypt는 장고 개발버전에서만 지원됩니다.

예로:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

User.set_password()User.check_password() 메소드(method)가 이 값들을 처리할 수 있습니다.

0.90같은 이전 장고 버전에서는 간단하게 Salt 없이 MD5가 사용됩니다. 이전 버전을 위해서 이 방법도 여전히 유효합니다; 다만 User.check_password()가 실행되면 자동으로 새로운 형식으로 변경됩니다.

익명사용자

django.contrib.auth.models.AnonymousUserdjango.contrib.auth.models.User를 본받았지만 몇 가지 차이점이 있습니다.:

  • id는 항상 None.
  • is_staffis_superuser는 항상 False.
  • is_active는 항상 True.
  • groupsuser_permissions는 항상 비어있습니다(empty).
  • is_anonymous()True.
  • is_authenticated()False.
  • has_perm()는 항상 False.
  • set_password(), check_password(), save(), delete(), set_groups() 그리고 set_permissions()NotImplementedError 예외(Exception)를 발생시킵니다.

실제로 AnonymousUser를 필요 없을지도 모르지만, 다음 섹션에서 설명하는 Web requests에서 사용됩니다.

관리자 만들기

settings.py의 INSTALLED_APPS'django.contrib.auth'를 추가한 상태에서 처음 manage.py syncdb를 실행시키면 관리자 계정을 만듭니다. 나중에 관리자 계정을 따로 만들려면 create_superuser.py를 사용할 수 있습니다.:

python /path/to/django/contrib/auth/create_superuser.py

/path/to/를 장고를 설치한 디렉토리로 바꿔주세요.

Web requests에서 인증하기

지금까지 이 문서는 인증과 관련된 객체(object)들을 다루는, 낮은 수준(low-level)에서 API를 설명했습니다. 장고는 인증 프레임워크(authentication framework)를 request objects?와 연결시킵니다.

먼저 장고의 미들웨어(middleware) 중에 SessionMiddlewareAuthenticationMiddleware를 settings.py의 MIDDLEWARE_CLASSES에 추가합니다. 자세한 내용은 session documentation?를 참고하세요.

한번 이들 미들웨어(middleware)를 설치하면 뷰(views)에서 request.user를 접근할 수 있습니다. request.user는 현재 로그인한 사용자를 표현한 User객체(object)를 가리킵니다. 사용자가 로그인하지 않았다면, AnonymousUser객체(object)를 대신 가리킵니다. (이전 섹션을 참고하세요.) 다음과 같이 is_authenticated()로 구분할 수 있습니다.:

if request.user.is_authenticated():
     # 인증된 사용자의 경우,
else:
     # 인증되지 않은 사용자의 경우,

로그인시키는 방법은

장고는 django.contrib.auth에서 두가지 함수(function)를 제공합니다.: authenticate()login().

사용자 이름과 패스워드로 인증하려면 authenticate() 함수(function)를 사용하세요. 이 함수는 usernamepassword라는 두가지 키워드 인수(keyword argument)를 받아서 인증에 성공할 경우 User객체를 반환합니다. 그렇지 않으면 None을 반환합니다. 예를 들면:

from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    if user.is_active:
        print "사용자이름과 비밀번호가 일치했습니다!"
    else:
        print "사용자 계정이 중단되었습니다!"
else:
    print "주어진 사용자 이름과 비밀번호가 정확하지 않습니다."

뷰(view)에서 사용자 로그인을 처리하려면 HttpRequestUser를 가지고 login() 메소드(method)를 사용하세요. login() 메소드(method)는 장고 세션 프레임워크(session framework)를 통해서 세션(session)에 사용자 ID를 저장합니다. 물론 세션 미들웨어(session middleware)를 설치했는지 확인해야합니다.

아래 authenticate()login()를 사용하는 예를 설명합니다.:

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        if user.is_active:
            login(request, user)
            # 인증된 사용자를 위한 페이지로 이동합니다.
        else:
            # '중지된 사용자'라는 메세지를 표시합니다.
    else:
        # Return an 'invalid login' error message.
        # 로그인되지 않았다는 에러 메세지를 표시합니다.

비밀번호를 손수 검사하는 방법

django.contrib.auth.models.check_password 함수(function)를 사용해서 글자로 표현된 비밀번호(plain-text password)와 데이터베이스에 hash로 저장된 비밀번호(hashed password)를 손수 비교해서 사용자를 인증할 수 있습니다. 두가지 인수(argument)가 필요합니다: 글자로 표현된 비밀번호와 데이터베이스에 저장된 hash처리된 비밀번호. 동일한 경우 True를 반환합니다. 그렇지 않을 경우에는 False.

로그아웃시키는 방법

django.contrib.auth.login()로 사용자를 로그아웃시키려면 뷰(view)에서 django.contrib.auth.logout()를 사용하기 바랍니다. HttpRequest가 주어져야 합니다. 예를 들면:

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # 로그아웃한 사용자를 위한 페이지로 이동합니다.

참고로 logout()은 로그인하지 않은 사용자인 경우에도 예외(Exception)를 발생시키지 않습니다.

접근 제한하기

낮은 수준에서 처리하는 방법

간단하게 request.user.is_authenticated()로 검사해서 원하는 로그인 페이지로 이동시킬 수 있습니다.:

from django.http import HttpResponseRedirect

def my_view(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/login/?next=%s' % request.path)
    # ...

...에러 메세지를 표시하도록 할 수도 있습니다.:

def my_view(request):
    if not request.user.is_authenticated():
        return render_to_response('myapp/login_error.html')
    # ...

로그인한 사용자를 체크하는 decorator

간단하게 사용하기 위해서 login_required decorator를 사용할 수 있습니다.:

from django.contrib.auth.decorators import login_required

def my_view(request):
    # ...
my_view = login_required(my_view)

Python 2.4부터 지원하는 decorator를 사용해서 좀더 간단하게 표시할 수 있습니다.:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # ...

장고 개발버전에서는 login_required decorator는 redirect_field_name이라는 키워드 인수(keyword argument)를 지정할 수 있습니다.:

from django.contrib.auth.decorators import login_required

def my_view(request):
    # ...
my_view = login_required(redirect_field_name='redirect_to')(my_view)

Python 2.4에서 decorator를 사용한 좀더 간략한 표현을 사용할 수 있습니다.:

from django.contrib.auth.decorators import login_required

@login_required(redirect_field_name='redirect_to')
def my_view(request):
    # ...

login_required는 다음과 같은 역할을 합니다.:

  • 로그인에 실패하면, settings.LOGIN_URL으로 이동(redirect)합니다. 이때 이 URL에는 query string으로 next를 이름으로 하는 redirect_field_name을 값으로 함께 가지게 됩니다.

예를 들면: /accounts/login/?next=/polls/3/.

  • 사용자가 정상적으로 로그인하면, 뷰(view)가 실행됩니다. 뷰(view)의 코드는 사용자가 로그인했다고 가정합니다.

그리고 settings.LOGIN_URL에 설정된 뷰(view)를 지정해야 합니다. 예를 들면 URLconf에 다음을 추가하세요.:

(r'^accounts/login/$', 'django.contrib.auth.views.login'),

django.contrib.auth.views.login가 하는 역할은:

  • GET 방식으로 호출하면 그냥 로그인 폼(form)을 보여줍니다.
  • POST 방식으로 호출하면, 로그인을 시도합니다. 로그인에 성공하면 뷰(view)는 next에 지정된 URL로 이동(redirect)합니다. next가 지정되어 있지 않으면 settings.LOGIN_REDIRECT_URL로 이동합니다. (기본값은 /accounts/profile/) 로그인에 실패하면 다시 로그인 페이지로 이동합니다.

기본으로 정해진 registration/login.html은 직접 구현해야 합니다. 이 템플릿(template)은 세가지 context 변수를 받습니다.

  • form: 로그인 폼(form)을 표현하는 FormWrapper객체(object). FormWrapper에 대해서는 forms documentation? 참고하세요.
  • next: 로그인에 성공한 뒤에 이동할 URL. query string도 포함할 수 있습니다.
  • site_name: 현재 SITE_ID를 참조한 Site의 이름. 장고 개발버전을 사용하고 사이트 프레임워크(site framework)를 사용하지 않는다면, request.META['SERVER_NAME']의 값이 사용됩니다. 사이트(site)에 관해서는 site framework docs?를 참고하세요.

registration/login.html 대신 템플릿(template)을 지정하고 싶으면, URLconf를 설정할 때 뷰(view)에 template_name을 인수(argument)로 지정할 수 있습니다. 예를 들면 myapp/login.html을 사용하고 싶다면:

(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'myapp/login.html'}),

registration/login.html 템플릿(template) 예입니다. 여기서는 base.html 템플릿(template)을 content 블록(content block)으로 사용한다고 설정했습니다.:

{% extends "base.html" %}

{% block content %}

{% if form.has_errors %}
<p>사용자 이름과 비밀번호를 지정하세요.</p>
{% endif %}

<form method="post" action=".">
<table>
<tr><td><label for="id_username">사용자 이름:</label></td><td>{{ form.username }}</td></tr>
<tr><td><label for="id_password">비밀번호:</label></td><td>{{ form.password }}</td></tr>
</table>

<input type="submit" value="로그인" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}

이외에 다른 뷰(view)들

login 뷰(view) 이외에 인증 시스템에는 몇가지 뷰(view)들을 추가로 제공하고 있습니다.

django.contrib.auth.views.logout

설명:

사용자를 로그아웃시킵니다.

추가 인수(argument):

  • template_name: 로그아웃시킨 다음 사용할 템플릿(template)의 전체 이름. 지정하지 않으면 registration/logged_out.html이 기본으로 사용됩니다.

템플릿(template) context:

  • title: "Logged out"(언어설정에 따라 번역됩니다).

django.contrib.auth.views.logout_then_login

설명:

로그아웃시킨 다음, 로그인 페이지로 이동(redirect)합니다.

추가 인수(argument):

  • login_url: 이동할 로그인 페이지의 URL. 지정하지 않으면 기본으로 settings.LOGIN_URL을 사용합니다.

django.contrib.auth.views.password_change

설명:

사용자가 패스워드를 변경할 수 있는 페이지를 제공합니다.

추가 인수(argument):

  • template_name: 패스워드를 변경할 수 있는 폼(form)을 포함한 템플릿(template) 이름을 지정합니다. 따로 지정하지 않으면 registration/password_change_form.html을 사용합니다.

템플릿(template) context:

  • form: 패스워드 변경에 필요한 폼(form).

django.contrib.auth.views.password_change_done

설명:

사용자 패스워드를 변경한 다음 보게될 페이지.

추가 인수(argument):

  • template_name: 사용할 템플릿(template) 전체 이름. 지정하지 않으면 registration/password_change_done.html을 사용합니다.

django.contrib.auth.views.password_reset

설명:

사용자가 패스워드를 초기화할 수 있도록 합니다. 새로운 비밀번호는 장고에서 지정하고 새 비밀번호는 이메일로 전달합니다.

추가 인수(argument):

  • template_name: 사용할 템플릿(template) 전체 이름. 따로 지정하지 않으면 registration/password_reset_form.html를 사용합니다.
  • email_template_name: 사용할 템플릿(template) 전체 이름. 따로 지정하지 않으면 registration/password_reset_email.html를 사용합니다.

템플릿(template) context:

  • form: 패스워드 초기화에 사용될 폼(form).

django.contrib.auth.views.password_reset_done

설명:

패스워드를 초기화하고나서 보여줄 페이지

추가 인수(argument):

  • template_name: 사용할 템플릿(template) 전체 이름. 따로 지정하지 않으면 registration/password_reset_done.html를 사용합니다.

django.contrib.auth.views.redirect_to_login

설명:

로그인페이지로 이동(redirect)시킵니다. 로그인 이후 다시 다른 URL로 이동시킵니다.

필요한 인수(argument):

  • next: 로그인에 성공한 뒤에 이동(redirect)할 페이지 URL

추가 인수(argument):

  • login_url: 이동할 로그인 페이지의 URL. 따로 지정하지 않으면 settings.LOGIN_URL을 사용합니다.

기본으로 사용할 수 있는 폼(form)

위에 설명한 인증과 관련된 뷰(view)를 사용하지 않으면서 장고가 제공하는 인증과 관련된 폼(form)을 그대로 사용하고 싶다면 아래 장고에서 제공하는 폼(form)들이 있습니다.:

  • django.contrib.auth.forms.AdminPasswordChangeForm: 관리자 화면에서 사용하는 사용자 비밀번호를 변경할 수 있는 폼(form)
  • django.contrib.auth.forms.AuthenticationForm: 사용자 로그인에 사용하는 폼(form)
  • django.contrib.auth.forms.PasswordChangeForm: 사용자가 패스워드를 변경할 수 있도록 하는 폼(form)
  • django.contrib.auth.forms.PasswordResetForm: 사용자 비밀번호 초기화에 사용하는 폼(form)
  • django.contrib.auth.forms.UserCreationForm: 새로운 사용자를 만드는 폼(form)

로그인된 사용자 접근 제한하기

허용권한(permission)에 의해서 접근을 제하는 방법은 이전 섹션에서 설명한 것과 다르지 않습니다.

간단하게 뷰(view)에서 request.user를 가지고 검사할 수 있습니다. 예를 들어서 아래 뷰(view)는 사용자가 로그인되었는지, poll.can_vote라는 허용권한(permission)을 가지고 있는지 검사합니다.:

def my_view(request):
    if not (request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
        return HttpResponse("투표할 수 없습니다.")
    # ...

더 간단하게 user_passes_test decorator를 사용할 수 있습니다.:

from django.contrib.auth.decorators import user_passes_test

def my_view(request):
    # ...
my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'))(my_view)

간단한 예를 들었습니다. 조금 뒤에 허용권한(permission) 여부만 검사할 수 있는 decorator를 말씀드립니다.

Python 2.4 이상에서는 decorator를 사용할 수 있습니다.:

from django.contrib.auth.decorators import user_passes_test

@user_passes_test(lambda u: u.has_perm('polls.can_vote'))
def my_view(request):
    # ...

user_passes_test는 인수(argument)를 필요로 합니다: User 객체(object)를 인수(argument)로 받아서 허용권한(permission)이 있으면 True를 반환하는 함수(function)입니다. user_passes_testUser객체(object)를 직접 검사하지 않습니다.

user_passes_test()는 또한 login_url을 인수(argument)로 받습니다. 반환되는 결과에 따라서 이동할 로그인 페이지URL을 지정할 수 있습니다. 지정하지 않으면settings.LOGIN_URL이 사용됩니다.

Python 2.3에서는:

from django.contrib.auth.decorators import user_passes_test

def my_view(request):
    # ...
my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')(my_view)

Python 2.4에서는:

from django.contrib.auth.decorators import user_passes_test

@user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')
def my_view(request):
    # ...

permission_required decorator

앞서 설명한 대로 permission_required() decorator는 사용자가 어떤 허용권한(permission)을 갖고 있는지 검사하는 기본적인 방법입니다.:

from django.contrib.auth.decorators import permission_required

def my_view(request):
    # ...
my_view = permission_required('polls.can_vote')(my_view)

permission_required()는 추가로 login_url을 인수(argument)로 받습니다. 예를 들어서:

from django.contrib.auth.decorators import permission_required

def my_view(request):
    # ...
my_view = permission_required('polls.can_vote', login_url='/loginpage/')(my_view)

login_required decorator처럼 login_url도 따로 지정하지 않으면 settings.LOGIN_URL이 기본으로 사용됩니다.

일반적인 뷰(view)에서 접근 제한하기

일반적인 뷰(view)(generic view?)에서 접근을 제한을 제한하려면 뷰(view)을 처리하는 함수(function)를 만들고 URLconf에 원래 뷰(view) 대신 함수(function)를 지정해줍니다. 예를 들어서:

from django.views.generic.date_based import object_detail

@login_required
def limited_object_detail(*args, **kwargs):
    return object_detail(*args, **kwargs)

허용권한(permission)

장고는 간단한 허용권한(permission) 시스템을 갖고 있습니다. 사용자와 사용자의 그룹에 허용권한(permission)을 할당하는 방법을 제공합니다.

장고 관리자 사이트에서 주로 사용되지만, 여러분의 사이트에 적용해보세요.

장고 관리 사이트는 아래와 같이 허용권한(permission)을 사용합니다.

  • 폼(form)을 통해서 새로운 객체(object)를 추가할 때 객체(object) type에 대한 "add"(더하기) 허용권한(permission)으로 뷰(view)에 대한 사용자 접근을 제한합니다.
  • 폼(form)을 통해서 객체(object)의 속성(attribute)을 변경할 때 객체(object) type에 대한 "change"(바꾸기) 허용권한(permission)으로 뷰(view)에 대한 사용자 접근을 제한합니다.
  • 폼(form)을 통해서 객체(object)를 지울 때 객체(object) type에 대한 "delete"(지우기) 허용권한(permission)으로 뷰(view)에 대한 사용자 접근을 제한합니다.

허용권한(permission)은 특정한 객체(object)에 대해서가 아니라, 객체(object) 타입 별로 전역적(globally)으로 지정됩니다. 예를 들어서 장고에서는 이렇게 말할 수 있습니다. "영희는 새로운 기사를 바꿀 수 있습니다." 하지만 "영희는 자기가 쓴 새로운 기사만 바꿀 수 있습니다." 혹은 "영희는 특정한 기사 상태나 공개여부 혹은 기사 번호의 새로운 기사를 바꿀 수 있습니다."라는 식으로는 불가능합니다. 이 부분은 장고 개발자들끼리 얘기 중입니다.

허용권한(permission)의 기본

세가지 기본 허용권한(permission) - 더하기(add), 바꾸기(change), 지우기(delete) --는 class Admin이 지정된 장고 모델(model)을 만들면 자동으로 부여됩니다. 좀 더 자세히 설명하자면, manage.py syncdb가 실행되면 데이터베이스에는 auth_permission 테이블이 생성되는데 이 테이블에 해당 모델(model)에 대한 허용권한(permission)이 저장됩니다.

물론 class Admin을 모델(model)에 지정하지 않은 채로 syncdb를 실행하면 허용권한(permission)은 부여되지 않습니다. class Admin를 모델(model)에 추가하고 다시 manage.py syncdb를 실행하세요. 그러면 빠졌던 허용권한(permission)이 새로 부여됩니다.

개발자가 정의한 허용권한(permission)

model Meta attribute?permissions을 추가해서 개발자가 직접 정의한 허용권한(permission)을 사용할 수 있습니다.

아래 모델(model)은 세가지 허용권한(permission)을 만들었습니다.:

class USCitizen(models.Model):
    # ...
    class Meta:
        permissions = (
            ("can_drive", "Can drive"),
            ("can_vote", "Can vote in elections"),
            ("can_drink", "Can drink alcohol"),
        )

이것도 또한 syncdb를 할 때 추가로 지정된 허용권한(permission)으로 추가됩니다.

API 참고

사용자 클래스(class)처럼 허용권한(permission)은 django/contrib/auth/models.py이 정의하는 장고 모델(model)에서 구현되어 있습니다.

필드(field)

Permission객체(object)는 아래와 같은 필드(field)를 가지고 있습니다.:

  • name - 반드시 필요. 최대 50자까지. 예: 'Can vote'.
  • content_type - 반드시 필요. django_content_type 테이블이 정의에 따릅니다.
  • codename - 반드시 필요. 최대 100자까지. 예: 'can_vote'.

메소드(method)

Permission객체(object)는 다른 Django model?에서처럼 데이터에 접근하는 일관된 메소드(method)를 제공합니다.

템플릿(template)에서 인증과 관련된 데이터

RequestContext를 사용할 경우 현재 로그인된 사용자와 이 사용자에 부여된 허용권한(permission)은 template context?를 통해서 접근할 수 있습니다.

좀더 자세히

이 변수들은 설정(settings) 중 TEMPLATE_CONTEXT_PROCESSORS"django.core.context_processors.auth"를 추가하고 RequestContext를 사용할 경우에만 가능합니다. 자세한 내용은 RequestContext docs?를 참고하세요.

사용자

User객체(object) 혹은 AnonymousUser객체(object) 모두 템플릿(template) 변수, {{ user }} 에 저장됩니다.:

{% if user.is_authenticated %}
    <p>안녕하세요, {{ user.username }}. 로그인하셨습니다.</p>
{% else %}
    <p>안녕하세요, 로그인하세요.</p>
{% endif %}

허용권한(permission)

로그인된 사용자의 허용권한(permission)은 템플릿(template) 변수, {{ perms }} 에 저장됩니다. {{ perms }} django.core.context_processors.PermWrapper의 객체(object)입니다.

{{ perms }} User.has_module_perms을 직접 가리킵니다. 즉, 어플리케이션(app) 이름으로 허용권한(permission)을 객체(object) 속성(attribute)으로 가져올 수 있습니다.:

{{ perms.foo }}

User.has_perms를 통해서 접근 여부를 허용권한(permission) 이름으로 객체(object) 속성(attribute)으로 가져올 수 있습니다.:

{{ perms.foo.can_vote }}

다음과 같이 템플릿(template)의 {% if %} 구문으로 허용권한(permission)을 검사할 수 있습니다.:

{% if perms.foo %}
    <p>foo 어플리케이션에 접근할 수 있습니다.</p>
    {% if perms.foo.can_vote %}
        <p>투표할 수 있습니다!</p>
    {% endif %}
    {% if perms.foo.can_drive %}
        <p>운전할 수 있습니다!</p>
    {% endif %}
{% else %}
    <p>foo 어플리케이션에서 할 수 있는 게 없군요.</p>
{% endif %}

그룹

그룹은 사용자를 분류하는 일반적인 방법입니다. 그룹으로 사용자들을 묶어서 허용권한(permission)을 적용하고 이름을 부여할 수 있습니다. 하나의 사용자는 여러 그룹에 속할 수 있습니다.

그룹에 포함된 사용자는 자동으로 그 그룹의 허용권한(permission)을 가지게 됩니다. 예를 들어서 Site editors라는 그룹은 can_edit_home_page라는 허용권한(permission)을 가지고 있고 이 그룹에 속한 사용자들도 can_edit_home_page 허용권한(permission)을 부여받게 됩니다.

허용권한(permission) 이외에 그룹은 사용자들을 쉽게 분류해서 관리할 수 있도록 해줍니다. 예를 들어서 'Special users'라는 그룹을 만들고 사이트에 이 그룹에 속한 사용자들만 접근할 수 있는 페이지를 제공하거나 이 그룹에 속한 사용자들 모두에게 메일을 보낼 수도 있습니다.

메세지

메세지 시스템은 사용자들에게 쉽게 메세지를 보낼 수 있는 큐(queue) 형태의 시스템입니다. (역자주: 장고에서 얘기하는 메세지 시스템은 메일을 발송을 의미하지 않습니다. 장고의 메세지 시스템은 페이지에 간단한 작업 결과 메세지를 표시하는 데 주로 사용됩니다. 아래 예제를 참고하세요.)

메세지는 User 객체(object)와 묶여 있습니다. 단 메세지를 보낸 시간은 기록하지 않습니다.

주로 메세지는 장고 관리 페이지에서 어떤 작업을 끝냈을 때 사용됩니다. 예를 들어서 "The poll Foo was created successfully."가 메세지입니다.

API는 간단합니다.:

  • user_obj.message_set.create(message='message_text')로 새 메세지를 만들 수 있습니다.
  • user_obj.get_and_delete_messages()는 이 사용자에게 전달된 Message를 리스트(list) 형태로 반환합니다.

아래 예에서, playlist를 만든 다음 사용자에게 메세지를 남깁니다.:

def create_playlist(request, songs):
    # 몇몇 노래로 노래 목록을 만듭니다.
    # ...
    request.user.message_set.create(message="노래 목록을 잘 만들었습니다.")
    return render_to_response("playlists/create.html",
        context_instance=RequestContext(request))

RequestContext을 사용할 수 있는 상태에서 로그인한 사용자의 메세지는 template context?의 템플릿(template) 변수로 가져올 수 있습니다.:

{% if messages %}
<ul>
    {% for message in messages %}
    <li>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

RequestContext는 메세지가 보이는 것과 상관 없이 get_and_delete_messages를 호출해서 사용자에게 할당된 메세지를 지웁니다.

장고 메세지 시스템은 데이터베이스에 등록된 사용자에게만 사용할 수 있습니다. 등록되지 않은 익명(anonymous) 사용자에게도 적용하고 싶다면 session framework?를 활용하는 방법이 있습니다.

이외 인증과 관련된 소스

장고의 인증 시스템은 대체로 일반적인 요구사항을 만족시키고 있습니다. 하지만, 다른 인증시스템과 연동하는 경우도 있을 수 있습니다.

예를 들어서, 여러분의 회사가 직원 계정을 LDAP로 관리하고 있는 경우에는 장고 인증시스템과 LDAP에서 각각 계정을 관리하기는 어려울 것입니다.

그래서 이런 상황을 해결하기 위해서 장고의 인증시스템은 다른 인증시스템을 이용할 수 있는 방법은 제공합니다. 장고의 기본 데이터베이스 구조를 다시 작성하거나 다른 인증시스템과 함께 사용할 수 있습니다.

인증 저장소(authentication backends)

장고는 내부적으로 "인증 저장소"의 목록을 가지고 있습니다. django.contrib.auth.authenticate()가 호출되면 - "로그인시키는 방법은"에서 설명한 것처럼 - 모든 인증 저장소를 사용해서 인증을 시도합니다. 첫번째 저장소(backend)에서 실패하면 다음 번 저장소(backend)로 시도하는 식으로 모든 저장소(backend)를 사용합니다.

인증 저장소(backend) 목록은 AUTHENTICATION_BACKENDS 설정에 튜플(tuple) 형태로 지정합니다.

기본적으로, AUTHENTICATION_BACKENDS는:

('django.contrib.auth.backends.ModelBackend',)

이것은 장고 사용자 데이터베이스를 다루는 기본 인증 저장소(backend)입니다.

AUTHENTICATION_BACKENDS에서는 순서가 중요합니다. 여러 저장소(backend)에 같은 사용자와 비밀번호가 있는 경우에는, 먼저 인증에 성공하는 저장소(backend)가 사용됩니다.

인증 저장소(authentication backend) 작성하기

인증 저장소(backend)는 두가지 메소드(method)만 구현하면 됩니다:get_user(user_id), authenticate(**credentials)

get_user 메소드(method)는 user_id를 인수(argument)도 받아서 User를 반환합니다. - 저장소(backend)에 저장된 사용자 이름입니다.

authenticate 메소드(method)는 키워드 인수(keyword argument)를 받습니다. 대부분 이런 형태를 보입니다.:

class MyBackend:
    def authenticate(self, username=None, password=None):
        # username과 password를 검사해서 ``User``를 반환합니다.

다음처럼 token을 받기도 합니다.:

class MyBackend:
    def authenticate(self, token=None):
        # token을 검사해서 ``User``를 반환합니다.

authenticate 메소드(method)는 인증조건(credentials)을 검사해서 인증을 통과하면 User를, 아니면 None을 반환합니다.

장고 관리 시스템은 이 문서 처음에서 설명한 것처럼 User 객체(object)와 밀접하게 물려있습니다. 따라서 다른 인증시스템(예를 들어서, LDAP 디렉토리 서비스, 혹은 외부 SQL 데이터베이스)와 연동하기 위해서 가장 좋은 방법은 사용자의 장고의 User객체(object)를 만드는 것입니다. 따라서 따로 스크립트를 작성하거나 사용자가 로그인할 때 authenticate 메소드(method)를 호출해줄 수도 있습니다.

settings.py에 정의된 사용자와 패스워드로 인증하는 예입니다.:

from django.conf import settings
from django.contrib.auth.models import User, check_password

class SettingsBackend:
    """
    ADMIN_LOGIN와 ADMIN_PASSWORD 설정으로 인증을 시도합니다.

    로그인 이름과 hash처리된 비밀번호를 사용합니다. 예를 들어:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
    """
    def authenticate(self, username=None, password=None):
        login_valid = (settings.ADMIN_LOGIN == username)
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # 새로운 사용자를 만듭니다. settings.py에 정의된
                # 패스워드를 그대로 사용합니다.
                user = User(username=username, password='get from settings.py')
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

다른 인증 저장소(custom backends) 다루기

다른 인증 저장소(backend)는 나름대로의 허용권한(permission)을 제공할 수 있습니다.

user 모델(model)의 허용권한(permission)에 접근하는 함수(function)들(get_group_permissions(), get_all_permissions(), has_perm(), 그리고 has_module_perms())은 인증 저장소(backend)가 구현한 함수(function)에 의존합니다.

사용자에게 부여된 허용권한(permission)은 모든 저장소(backend)가 가진 모든 허용권한(permission)의 일부분입니다. 즉, 사용자는 어떤 인증 저장소(backend)가 허용한다면 허용권한(permission)을 가질 수 있습니다.

간단한 저장소(backend)를 구현한 예입니다. 관리자의 허용권한(permission)을 검사합니다.:

class SettingsBackend:

    # ...

    def has_perm(self, user_obj, perm):
        if user_obj.username == settings.ADMIN_LOGIN:
            return True
        else:
            return False

위 예는 관리자에게 모든 허용권한(permission)을 허용합니다. 인증 저장소(backend)의 함수(function)들은 User객체(object)와 User 메소드(method)가 받은 인수(argument)를 함께 전달받습니다.

인증에 관해서는 auth_permission 테이블을 사용하는 기본 인증 저장소(backend)인 django/contrib/auth/backends.py를 참고하세요.