Djangoプロジェクト間を OAuth2 連携する

(コメント)

ローカル環境に Django プロジェクトを2つ作り、OAuth2で(ダミーの)プロフィール情報を取得するまで書きます。

2つの Django プロジェクトを作ります。test_provider, test_consumer です。

OAuth2の認証フローは、サーバ間通信のため「Authorization Code」形式で行います。

OAuth2についての情報は IPAのページ などにあります

ソースコードを github に上げました。

Django プロジェクト 概要

test_provider

起動ポート 8000
ユーザーのIDとハッシュ化パスワードを保持します。
OAuth2 プロバイダを提供します。
ユーザーに test_provider への情報提供の認可を問います。
django-oauth-toolkit を使います

test_consumer

起動ポート 8001
ユーザーが最初にアクセスする Webページ
ユーザーに test_provider への認可ページへ誘導します。
ユーザー認可後は、このプログラムが test_provider へリクエストを行い、情報をもらいます。
django-allauth を使います。

※なお、django-allauth と同じくらい使いやすいのが python-social-auth です。Django のライブラリも含んでいます。

django-social-auth というライブラリも見かけると思いますが、Python3に対応していないのでやめておきましょう。

OAuth2プロバイダの開発

django-oauth-toolkit の素晴らしいドキュメントがここにあります。
「Make a Provider in a Minute」という挑発的なタイトルですが、
認証モデルにこだわりがなければほんとにすぐ作れます。
がんばっても1分では作れないと思いますが…。

Django プロジェクトの作成

$ mkdir test-oauth
$ cd test-oauth

$ django-admin.py startproject test_provider
$ pip install django-oauth-toolkit django-cors-headers

$ cd test_provider

test_provider/settings.py の編集

INSTALLED_APPS に追加

    'oauth2_provider',
    'corsheaders',

MIDDLEWARE_CLASSES に追加

'corsheaders.middleware.CorsMiddleware',

末尾に追加

CORS_ORIGIN_ALLOW_ALL = True

test_provider/urls.py の編集

from django.conf.urls import url, include
from django.contrib import admin
from .views import profile_view

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
    url(r'^api/profile/$', profile_view, name='profile'),
    url(r'^accounts/login/', 'django.contrib.auth.views.login', name='login',
        kwargs={'template_name': 'admin/login.html'}),
]

test_provider/views.py の作成

from django.http import JsonResponse
from oauth2_provider.views.generic import ProtectedResourceView

class ProfileView(ProtectedResourceView):
    def get(self, request, **kwargs):
        user = request.resource_owner

        return JsonResponse({
            'user_id': user.id,
            'email': user.email,
            'date_joined': user.date_joined,
            'secret_message': 'The quick brown fox',
        })

profile_view = ProfileView.as_view()

DB に反映

$ ./manage.py migrate

Adminユーザーの作成

OAuthプロバイダを追加設定するためには、Adminユーザーが必要なため作っておきます。

$ ./manage.py createsuperuser

テストサーバの起動

$ ./manage.py runserver 8000

OAuthプロバイダの設定

プロバイダの設定ページにアクセスするために、一度 Admin サイトにログインしておきます。

http://127.0.0.1:8000/admin/

Admin サイトにログイン後、アプリ追加ページを開きます。

http://127.0.0.1:8000/o/applications/

Click here をクリックします。

Register a new application ページとなるので、

Name: test-consumer (何でも良い)
Client type: Confidential
Authorization grant type: Authorization code
Redirect urls:
http://localhost:8001/accounts/testprovider/login/callback/
http://127.0.0.1:8001/accounts/testprovider/login/callback/

このように入力し、saveします。

作成した OAuthプロバイダは、このツールで簡単なテストができます。

client_id に先ほどの client_id をコピペし、
Authorization url は http://localhost:8000/o/authorize/ を入れればなんとなくのテストができます。

テストユーザーの作成

Admin から、テストユーザーを作っておきます。

http://127.0.0.1:8000/admin/auth/user/

ADD USER をクリックし、適当なユーザーを作っておいてください。
OAuth2 でのログイン時、このユーザーID/パスワードでログインを行うことになります。

引き続き、OAuthコンシューマの開発を行います。

起動中の Django テストサーバは、そのまま起動させっぱなしにしておいてください。

OAuth2コンシューマテストアプリの作成

Django プロジェクトの作成

$ cd test-oauth

$ django-admin.py startproject test_consumer
$ pip install django-allauth

$ cd test_consumer

ここでは、OAuth クライアントライブラリ django-allauth をインストールしています。

django-allauth は多彩はクライアント接続に対応した、多機能なライブラリで、django1.9 + Python 3.5 でも動きますので使い勝手が良いです。

django-allauth のドキュメントはここ

設定

http://django-allauth.readthedocs.io/en/latest/installation.html

test_consumer/settings.py

TEMPLATES の OPTIONS の context_processors に追加

'django.template.context_processors.request',

Django 1.9 なら、デフォルトで入ってると思います。

INSTALLED_APPS に追加

    'django.contrib.sites',
    'test_consumer',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    'allauth.socialaccount.providers.twitter',
    'testprovider',  # これから作る

allauth.socialaccount.providers.* は任意に選んで追加してください。
テストのために google と twitter を追加しています。

どんなプロバイダが準備されているかは公式ドキュメントに書いてあります。

http://django-allauth.readthedocs.io/en/latest/installation.html

末尾に追加

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
)

SITE_ID = 1
SESSION_COOKIE_NAME = 'test-consumer-session-id'

test_consumer/urls.py

from django.conf.urls import url, include
from django.contrib import admin
from django.views.generic import TemplateView

urlpatterns = [
    url(r'^admin/', admin.site.urls),

    url(r'^$', TemplateView.as_view(template_name='index.html')),
    url(r'^accounts/', include('allauth.urls')),

]

テンプレートの作成

$ mkdir test_consumer/templates
test_consumer/templates/index.html を作成

{% load socialaccount %}
{% load account %}

<html>
<head>
<meta charset="utf-8" />
</head>
<body>

<a href="{% provider_login_url "twitter" %}">Twitterでログイン</a><br />
<a href="{% provider_login_url "google" %}">Googleでログイン</a><br />
<a href="{% provider_login_url "testprovider" %}">Test Provider でログイン</a><br />

<hr />
{% if user.is_authenticated %}
  ログインユーザー: {% user_display user %}<br />
  {% for sa in user.socialaccount_set.all %}
    {{ sa.extra_data }}<br />
  {% endfor %}
{% endif %}
</body>
</html>

testprovider アダプタの作成

allauth に入っている、google のアダプタを改修して作るのが最も手軽でしょう。

allauth/socialaccount/providers/google このディレクトリをまるごとコピーし、
testprovider としてペーストします。 ( manage.py がいるディレクトリに)

内容は以下のようになります。

testprovider/provider.py

from allauth.socialaccount import providers
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider

class TestAccount(ProviderAccount):

    def to_str(self):
        dflt = super(TestAccount, self).to_str()
        return self.account.extra_data.get('name', dflt)

class TestProvider(OAuth2Provider):
    id = 'testprovider'
    name = 'Test Provider'
    account_class = TestAccount

    def get_default_scope(self):
        return ['read', 'write']

    def get_site(self):
        settings = self.get_settings()
        return settings.get('SITE', 'testprovider')

    def extract_uid(self, data):
        uid = str(data['user_id'])
        return uid

    def extract_common_fields(self, data):
        return dict(username=data.get('email', 'no name'))

providers.registry.register(TestProvider)

testprovider/urls.py

from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from .provider import TestProvider

urlpatterns = default_urlpatterns(TestProvider)

testprovider/views.py

import requests

from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter,
                                                          OAuth2LoginView,
                                                          OAuth2CallbackView)
from allauth.socialaccount.providers import registry

from .provider import TestProvider

from django.conf import settings

server_url_prefix = getattr(
    settings, 'TEST_PROVIDER_URL_PREFIX',
    'http://127.0.0.1:8000')

class TestOAuth2Adapter(OAuth2Adapter):
    provider_id = TestProvider.id
    access_token_url = server_url_prefix + '/o/token/'
    authorize_url = server_url_prefix + '/o/authorize/'
    profile_url = server_url_prefix + '/api/profile/'

    def complete_login(self, request, app, token, **kwargs):
        provider = registry.by_id(app.provider)
        resp = requests.get(self.profile_url,
                            params={'access_token': token.token})

        extra_data = resp.json()
        return self.get_provider().sociallogin_from_response(
            request, extra_data)

oauth2_login = OAuth2LoginView.adapter_view(TestOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(TestOAuth2Adapter)

DBのマイグレーション

$ ./manage.py migrate

Adminユーザーの作成

$ ./manage.py createsuperuser

テストサーバの起動

$ ./manage.py runserver 8001

アプリケーションの登録

http://127.0.0.1:8001/admin/

Admin サイトにログイン後、SOCIAL ACCOUNTS の Social applications の +Add をクリックします。

Provider: Test Provider
Name: Test Provider
Client id: 先ほどの 8000 admin で作った Client id
Secret key: 先ほどの 8000 admin で作った Secret key
Key: 空
Sites: example.com を選択

これで、Save します。

Google, Twitter など他サービスへのログインも必要であれば、ここから追加できます。

クライアントとしての動作テスト

先ほど、8000ポートにログインしたブラウザとは別のブラウザを使います。(ログアウト状態になっているため)

http://127.0.0.1:8001/

ここで、「Test Provider でログイン」をクリックします。

( AllAuth が自動的にログインフォームを提供してますので、このURLからでもいけます。/accounts/login/ )

そうすると、8000 ポートへ転送され、ログインフォームが出てきます。
今回は時間節約のため、Django Admin のログインフォームを流用していますが、
通常は自作のログインフォームをここで表示させることになると思います。

8000 (test_provider) で作ったテストユーザーでログインしてみましょう。

ログインが成功すると、認可を求めるページとなりますので、「Authorize」をクリック。
ちなみに、この認可を求めるページは、8000 (test_provider) の Admin の アプリ設定の、「Skip authorization」にチェックを入れると省略できます。

ログインに成功すると、8001 ポートに戻ってきます。

http://127.0.0.1:8001/accounts/profile/
が表示されますが、このページはまだ作っていませんので Not found になります。

情報取得は成功していますので、トップページのURLを手で入力して情報を見てみます。

http://127.0.0.1:8001/

トップページ下部に、API取得した extra_data を表示する箇所があります。
そこに、OAuth2 のプロバイダから取得した情報が表示されるのが確認できます。

OAuthプロバイダの /api/profile/ から取得した 'secret_message': 'The quick brown fox' が、OAuthコンシューマで取得できているのが確認できます。

ソースコードは Github に上げました。

現在の評価: 5

コメント

検索

最近のツイート

  • ytyng

    ytyng @ytyng

    やばい、機能がリリースされない! タイムゾーンか? サマータイム設定になってるのか? とか焦ってたら、リリース日になってなかっただけだった。今日月末じゃん。月初じゃないじゃん。1人で焦ってたけど結果1日得した気分
    3 週間 前

  • ytyng

    ytyng @ytyng

    class SerialCode って書こうとして、class Serialcode (非キャメルの1単語) の方がいいのではと思ってしまう。Datasturcture, Javascript みたいに非キャメル1単語で書きそうになるのは症状名があるのだろうか
    2 ヶ月 前

  • おてふ

    おてふ @otef

    ytyng

    ほんとうに頭がおかしいコマしかないのに読後感は非常に爽やかという奇跡のマンガ、男日本海が今なら無料なのでみなさん読んでおいたほうがいいですよほんとに https://t.co/AsIZsNuwiP https://t.co/kKl77lJIlH
    2 ヶ月, 1 週間 前

  • ytyng

    ytyng @ytyng

    昼間なのに影が多方向に出るのってちょっと面白い https://t.co/x1scFI2Uk2
    2 ヶ月, 2 週間 前

  • ytyng

    ytyng @ytyng

    https://t.co/FUChcVIVpm になって、パッケージの登録ができないんですけど、Python有識者の方にどうしたらいいか教えてほしい。 https://t.co/AEkCfbml6Z このガイドの通り twine 使っても HTTPError 410 っていう
    2 ヶ月, 2 週間 前