Djangoのユーザ認証まとめ

追記: 使用しているDjangoのバージョンをはじめ、いくつか古くなってきている点があります。DjangoCongress JP 2018で認証に関する発表を行ったのですが、ブログ記事も用意しています。そちらを参照してください

nwpct1.hatenablog.com

追記終わり


Djangoでユーザ認証をしたかったので調べてみると,DjangoではUserモデルがはじめから用意されているらしい.そこでDjangoの提供する機能をそのまま使ってログイン・ログアウトを実装してみた. さらに調べてみるとpython-social-authというライブラリを使えば、TwitterFacebookGoogleGithub等のアカウントを使ったOAuth認証が簡単に実装できるみたいなのでそれも試しておく.

ソースコードGithubで公開してます.

環境はPython 3.4、Django 1.7を使用しています.

目次

Djangoの提供する機能でログイン・ログアウト

準備

まずプロジェクトを作成する。

$ django-admin.py startproject user_auth
$ python manage.py migrate
$ python manage.py createsuperuser
  : スーパユーザを作成
$ python manage.py runserver

ブラウザで http://127.0.0.1:8000/admin を開いてログイン出来るか確認.

うまく行ったなら,次はアプリケーションを作成する.認証用に独立したアプリケーションを用意したほうが良さそうなので,accountsというアプリケーションを作成した.

$ python manage.py startapp accounts

user_auth/settings.pyを開いてINSTALLED_APPSにアプリケーションを追加.ついでに言語とタイムゾーンの設定もしておく.

INSTALLED_APPS = (
    :
    'accounts',
)

LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'


ログイン・ログアウトページの実装

djangoにはログイン・ログアウト用のviewがデフォルトで用意されているので,それをurls.pyに指定するだけでいい.

urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls)),
    url(r'^login/$', 'django.contrib.auth.views.login',
        {'template_name': 'accounts/login.html'}),
    url(r'^logout/$', 'django.contrib.auth.views.logout',
        {'template_name': 'accounts/logged_out.html'}),
)

ちなみに,registrationフォルダを作成して,その中にhtmlファイルを置く場合には,,{'template_name': 'path/to/template'}が省略できる. 今回はaccounts/templates/accounts/login.htmlに配置したため上記のように設定する必要がある.

  • accounts/templates/accounts/login.html
{% extends "base.html" %}

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
<table>
<tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
</tr>
<tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
</tr>
</table>

<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}
  • accounts/templates/accounts/logged_out.html
{% extends "base.html" %}

{% block title %}ログアウト{% endblock title %}

{% block content %}

    <h3 class="page-header">ログアウトしました</h3>

{% endblock content %}

このままだと,ログインフォームにユーザ名とパスワードを入力してログインしても,Page not foundが表示される.デフォルトでは,ログインに成功するとhttp://127.0.0.1:8000/accounts/profile/このようなURLにリダイレクトされるので,そこにユーザのプロフィールページ等を置く.

もし他の場所にリダイレクトさせたい場合は,settings.pyの中でLOGIN_REDIRECT_URLを指定する.


ログイン状態の確認

login_requiredデコレータを使えば,ページの閲覧をログイン済みのユーザだけに制限することが出来る. ログインしてないならsettings.pyLOGIN_URLに設定したパスに?next=requesst.pathを付けて飛ばす

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    ...

login_requiredデコレータを使わない場合は以下の様にする.

from django.http import HttpResponseRedirect

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

ここまでの内容は↓のcommitのdiffを見るのが分かりやすいかと思います.

Implement login and logout · c-bata/django-auth-example@dbea95f · GitHub


Userモデルをカスタマイズ

参考

上の記事によるとDjango1.5以降ならAbstractUserモデルを実装すればいいらしい.

  • accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    image_url = models.URLField('画像URL', blank=True)
  • accounts/admin.py
from django.contrib import admin
from accounts.models import CustomUser

admin.site.register(CustomUser)
  • user_auth/settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'

実行

$ python manage.py makemigration
$ python manage.py migrate
$ python manage.py runserver

管理サイトにログインする.ログインできなくなっていた.コンソールで確認する.

>>> from accounts.models import CustomUser
>>> CustomUser.objects.all()
[]
>>> from django.contrib.auth.models import User
>>> User.objects.all()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/Users/masashi/.virtualenvs/itukalist/lib/python3.4/site-packages/django/db/models/manager.py", line 230, in __get__
    self.model._meta.object_name, self.model._meta.swapped
AttributeError: Manager isn't available; User has been swapped for 'accounts.CustomUser'

どうやらもう一度createsuperuserする必要があるらしい.

$ python manage.py createsuperuser
  : adminアカウントを作成

ログインしてみると今度はうまく行った! adminの中身を見てみると画像URLの項目ができていることが確認できる.

スクリーンショット 2015-01-10 22.29.08.png

他の確認方法

>>> from django.contrib.auth import get_user_model
>>> get_user_model()
<class 'account.models.CustomUser'>

補足

今回は試してないが,AbstractBaseUserを使うと自前で1から定義できるらしい. もし必要なら↓の記事が役に立ちそう.

Django 1.5のカスタムユーザーモデルとdjango-registration その2 - Misc Notes


Userモデルをキーとして新しいモデルを定義する

ブログやタスク管理アプリなどを作るなら,User以外にもモデルを定義しなければならない. ブログ記事やタスク等はUserモデルに紐付けたいため,Userモデルの外部キーとして定義する.

accountsアプリケーションは認証機能だけにしたいので,新たなアプリケーションを作ってそこにタスクモデルを作成する.

$ python manage.py startapp api
  • user_auth/settings.py
INSTALLED_APPS = (
    :
    'api',
)
  • api/models.py

Two Scoops of Django: Best Practices for Django 1.8 によると、settings.AUTH_USER_MODEL を使うらしい。 get_user_model() を使ってはいけない。importがloopするらしい。

from django.db import models
from user_auth import settings


class Task(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='tasks')
    summary = models.CharField('タスク', max_length=128)
    complete = models.BooleanField('状態', default=False)
    comment = models.CharField('コメント', max_length=512, blank=True)
    done_date = models.DateField('完了日', null=True, blank=True)

    def __str__(self):
        return str(self.id) + ': ' + self.summary
  • api/admin.py
from django.contrib import admin
from api.models import Task

admin.site.register(Task)

これらを追記したらマイグレーションして実行してみる.

$ python manage.py makemigration
$ python manage.py migrate
$ python manage.py runserver

管理システムにログインすると,Taskモデルが追加されている. 試しにUserモデルとTaskモデルのレコードを追加してみて確認したほうがいいかもしれない.


DjangoTwitterFacebookGoogleGithub認証

Python Social Authを使ってThird-party authenticationを行う.

準備

次はWebブラウザから確認するためのアプリケーションなのでwebという名前で作成する.

$ python manage.py startapp web
  • user_auth/settings.py
INSTALLED_APPS = (
  :
  'web',
)
  • web/views.py
from django.shortcuts import render_to_response
from django.template import RequestContext


def home(request):
    context = RequestContext(request,
                             {'user': request.user})
    return render_to_response('web/home.html',
                              context_instance=context)
  • web/templates/web/home.html

templatesディレクトリは無ければ作成.

{% extends "base.html" %}

{% block title %}ホーム{% endblock title %}

{% block content %}
    <div>
        <h1>ホーム</h1>

        <p>
            {% if user and not user.is_anonymous %}
                Hello {{ user.get_full_name|default:user.username }}!
            {% else %}
                ログインして下さい.
            {% endif %}
        </p>
    </div>
{% endblock content %}

base.htmlは,accounts/templates/からプロジェクトルート直下のtemplatesディレクトリ内に移動.

  • user_auth/urls.py

以下を追加

url(r'^$', 'web.views.home', name='home'),

ここまで追加したら動かしてみる. ログインしてからホームに行くと,ユーザ名が表示される.


python-social-auth

$ pip install python-social-auth
  • user_auth/settings.py

AUTHENTICATION_BACKENDSの中にFacebookとかGithubとか追加していけば良いけど, 登録が面倒なので今回はTwitterだけ.

INSTALLED_APPS = (
  :
  'social.apps.django_app.default',
)

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'django.core.context_processors.media',
    'django.core.context_processors.static',
    'django.core.context_processors.tz',
    'django.contrib.messages.context_processors.messages',
    'social.apps.django_app.context_processors.backends',
    'social.apps.django_app.context_processors.login_redirect',
)

AUTHENTICATION_BACKENDS = (
    'social.backends.twitter.TwitterOAuth',
    'django.contrib.auth.backends.ModelBackend',
)

LOGIN_REDIRECT_URL = '/'
SOCIAL_AUTH_TWITTER_KEY = 'Your Twitter Key'
SOCIAL_AUTH_TWITTER_SECRET = 'Your Twitter Secret'

補足

ログインはhref="{% url 'social:begin' 'twitter' %}"のようにすれば開始できるが,href="{% url 'social:begin' 'twitter' %}?next={{ next }}"のようにすれば,ログイン後nextで指定したパスにリダイレクトさせる事ができる.nextを指定しなかった場合settings.pyLOGIN_REDIRECT_URLにリダイレクトする.

  • web/templates/web/home.html
{% extends "base.html" %}

{% block title %}ホーム{% endblock title %}

{% block content %}
    <div>
        <h1>ホーム</h1>

        <p>
        <ul>
            {% if user and not user.is_anonymous %}
                <li>
                    <a>Hello {{ user.get_full_name|default:user.username }}!</a>
                </li>
                <li>
                    <a href="{% url 'auth:logout' %}?next={{ request.path }}">Logout</a>
                </li>
            {% else %}
                <li>
                    <a href="{% url 'social:begin' 'twitter' %}?next={{ request.path }}">Login with Twitter</a>
                </li>
            {% endif %}
        </ul>
        </p>
    </div>
{% endblock content %}
  • web/views.py
from django.shortcuts import render_to_response
from django.template import RequestContext


def home(request):
    context = RequestContext(request,
                             {'request': request,
                              'user': request.user})
    return render_to_response('web/home.html',
                              context_instance=context)

追記できたら、migrateしてrunserver. トップページからTwitterログインのボタンを押すと,認証が出来る. 認証後,管理サイトから確認すると自分のTwitterアカウントがCustomUserの中に追加されている.

終了!


終わりに

アカウント作成の手間を考えるとpython-social-authを使ってthird-party authenticationの方に利点がある.これからWebアプリ書く時はpython-social-authガンガン使っていこう.

↓の本にベストプラクティスが詰まっててとても良かった。

Two Scoops of Django: Best Practices for Django 1.8

Two Scoops of Django: Best Practices for Django 1.8