Code


Version 2 (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.py``의 ``INSTALLED_APPS`` 항목에
포함되어 있습니다. ``INSTALLED_APPS``에 ``'django.contrib.auth'``가
이미 포함되어 있다면 ``manage.py syncdb``로 몇번이든 새로 인증시스템을
설치할 수 있습니다.

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

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

사용자
=====

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

.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py

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``를 가집니다.:
``groups``과 ``user_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``를 반환합니다. 이것으로
      ``User``와 ``AnonymousUser``를 구분할 수 있습니다. 일반적으로
      ``is_authenticated()``을 사용하는 것이 유리합니다.

    * ``is_authenticated()`` -- 항상 ``True``를 반환합니다. 사용자가
      인증을 통과했는지 말해줍니다. 단, 이 메소드(method)는 사용자의
      허용권한(permission)이 아니라 사용자가 정확한 ``username``과
      ``password``로 인증을 통과했는지만 판단합니다.

    * ``get_full_name()`` -- ``first_name``과 ``last_name``를
      빈칸(space)으로 묶어서 가져옵니다.

    * ``set_password(raw_password)`` -- 사용자 비밀번호를 지정합니다.
      ``User``객체(object)에 직접 비밀번호를 저장하지 마세요.

    * ``check_password(raw_password)`` -- 주어진 사용자 비밀번호가
      정확한지 검사합니다.

    * ``set_unusable_password()`` -- **장고 개발버전에 새롭게 추가**
      비밀번호를 사용하지 않도록 합니다. 단, 이것은 ``password``
      필드(field)를 빈공백(blank string)으로 처리하는 것과는 다릅니다.
      ``password`` 필드(field)에 빈 공백(blank string)으로 저장하면
      ``check_password``는 ``True``를 반환하지 않습니다.
      ``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_active``가 ``False``인 경우 항상 ``False``를 반환합니다.

    * ``has_perms(perm_list)`` -- ``"package.codename"``에 명시된
      허용권한(permission)들을 가지고 있는지 검사합니다.
      ``is_active``가 ``False``인 경우 항상 ``False``를 반환합니다.

    * ``has_module_perms(package_name)`` -- 사용자가 특정한 장고
      패키지(package)과 관련된 허용권한(permission)을 가지고 있는지
      검사합니다. ``is_active``가 ``False``인 경우 항상 ``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)가 발생합니다.

.. _Django model: ../model-api/
.. _DEFAULT_FROM_EMAIL: ../settings/#default-from-email

관리 함수(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.AnonymousUser``는
``django.contrib.auth.models.User``를 본받았지만 몇 가지 차이점이 있습니다.:

    * ``id``는 항상 ``None``.
    * ``is_staff``와 ``is_superuser``는 항상 ``False``.
    * ``is_active``는 항상 ``True``.
    * ``groups``와 ``user_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) 중에 ``SessionMiddleware``와
``AuthenticationMiddleware``를 settings.py의 ``MIDDLEWARE_CLASSES``에
추가합니다. 자세한 내용은 `session documentation`_를 참고하세요.

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

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

.. _request objects: ../request_response/#httprequest-objects
.. _session documentation: ../sessions/

로그인시키는 방법은
--------------------

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

사용자 이름과 패스워드로 인증하려면 ``authenticate()``
함수(function)를 사용하세요. 이 함수는 ``username``와 ``password``라는
두가지 키워드 인수(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)에서 사용자 로그인을 처리하려면 ``HttpRequest``와 ``User``를
가지고 ``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 %}

.. _forms documentation: ../forms/
.. _site framework docs: ../sites/

이외에 다른 뷰(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_test``은
``User``객체(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)

.. _generic view: ../generic_views/

허용권한(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)으로
추가됩니다.

.. _model Meta attribute: ../model-api/#meta-options

API 참고
-------------

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

.. _django/contrib/auth/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/models.py

필드(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`_를 통해서 접근할 수
있습니다.

.. admonition:: 좀더 자세히

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


   .. _RequestContext docs: ../templates_python/#subclassing-context-requestcontext

사용자
-----

``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 %}

.. _template context: ../templates_python/

그룹
======

그룹은 사용자를 분류하는 일반적인 방법입니다. 그룹으로 사용자들을
묶어서 허용권한(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`_를 활용하는 방법이 있습니다.

.. _session framework: ../sessions/

이외 인증과 관련된 소스
============================

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

예를 들어서, 여러분의 회사가 직원 계정을 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``를 참고하세요.

.. _django/contrib/auth/backends.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/backends.py