7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Django Rest FrameworkでOAuth認証をする

Posted at

本記事のゴール

  • OAuthの認可サーバーに対してリクエストをしてアクセストークンが返ってくること
  • OAuthのリクエスト&レスポンスをDjango Rest Swaggerのdocsに表示させること

環境

python==3.7.2
Django==2.1.7
django-rest-framework==3.9.3
django-rest-swagger==2.2.0
django-oauth-toolkit==1.2.0

OAuth認証には
django-oauth-toolkitを使用します
パッケージによるDRF認証

解説

基本的な設定

基本的には公式に習ってます
細かいところは独自に実装したので、その辺も踏まえて解説します

公式の1分でプロバイダを作るという項目があるように、このページを見れば簡単に実装できます
上記の項目を見れば誰でもできるので、この辺りは駆け足で解説します

settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'oauth2_provider',
    'corsheaders',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    )
}

OAUTH2_PROVIDER = {
    # 発行するアクセストークンにアクセス制御をつけるオプション
    'SCOPES': {
        'read': 'Read scope',
        'write': 'Write scope',
        'groups': 'Access to your groups'
    },

    # アクセストークンの有効期限秒数 2592000 -> 1ヶ月
    'ACCESS_TOKEN_EXPIRE_SECONDS': 2592000

プロジェクトルートのurls.pyを以下のように設定します

urls.py
urlpatterns = [
    path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),

]

クライアントID・クライアントシークレットの生成

完了後にmigrateするとテーブルが4つ追加されます
これらはクライアントID・アクセストーンなどの情報を保存するためのテーブルです。

その後、スーパーユーザーで以下のURLへアクセスしてください
http://127.0.0.1:8000/oauth/applications/
こんな画面が出ると思います

スクリーンショット 2019-05-11 11.32.12.png

Click hereを押してアプリケーション登録をしましょう。

スクリーンショット 2019-05-11 11.38.01.png

name -> 何でもいいです
Client id -> 自動で生成されます
Client secret -> 自動で生成されます
Client type -> confidential
Authorization grant type -> Recource ~~ passwordを選択
Redirect urls -> 空欄で大丈夫です

Saveを押すと情報がoauth_provider.applicationテーブルに保存されます
これでアプリケーション登録は完了です。

アクセストークン発行

次は生成したクライアント情報を使用してアクセストークンを発行しましょう。
http通信ができれば何でもいいのでコマンドでもいいのですが、個人的にはPostmanがオススメです

こうします(投げやり)
スクリーンショット 2019-05-11 11.54.08.png

レスポンスにアクセストークンが返ってきています
有効期限(expires_in)もsettings.pyで指定した値となっていますね
この時のレスポンス情報はoauth_prodider.accesstokenテーブルに保存されますので、ご確認ください

以上で基本的な説明は終わりですが、ここからはこのリクエスト&レスポンスをDjango Rest Swaggerに表示させるところまでやります
そこも知りたい!という方はもう少しお付き合いください。

Oauth認証のリクエスト&レスポンスをdocsへ表示

dJango-oauth-toolkitのパッケージにはDRF用に作成されたシリアライザーなどがないので、独自に実装する必要があります
djangorest-jwtは事前に用意されているので、docsに表示させるのも簡単にできるのですが・・・。
多少強引な実装となっておりますが、それでも良いって方はぜひ。

まずはシリアライザーがないとどうしようもないので、作りましょう

serializers.py
class OAuthPostTokenSerializer(serializers.Serializer):
    grant_type = serializers.CharField(required=True)
    username = serializers.CharField(required=True)
    password = serializers.CharField(required=True)
    client_id = serializers.CharField(required=True)
    client_secret = serializers.CharField(required=True)

次にシリアライザーを使ってviewを作ります

views.py
from rest_framework.generics import CreateAPIView
from .serializers import OAuthPostTokenSerializer

class OAuthPostToken(CreateAPIView):
    """ユーザー認証をしてアクセストークンを返す"""
    serializer_class = OAuthPostTokenSerializer

    def post(self, request, *args, **kwargs):
        protocol = self.request.scheme
        host = request.get_host()

        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            r = requests.post(protocol + '://' + host + '/oauth/token/',
                              data={
                                  'grant_type': 'password',
                                  'username': OAUTH_USERNAME,
                                  'password': OAUTH_PASSWORD,
                                  'client_id': OAUTH_CLIENT_ID,
                                  'client_secret': OAUTH_CLIENT_SECRET,
                              },
                              )
            return Response(r.json(), status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors)

一旦docsに持ってきてOauth認可サーバーに渡すという男らしい実装です笑
grant_type, client_id, client_secretは固定なので、環境変数として始めから値を持ってしまっても良いでしょう
最後にurls.pyに実装したviewを追加しましょう

urls.py
API_ROOT  = 'api/test/'
urlpatterns = [
    path(API_ROOT + 'oauth/token/', OAuthPostToken.as_view()), # <- 追加
    path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),

]

するとdocsにOauthが追加されています

スクリーンショット 2019-05-11 12.23.05.png

レスポンスも問題なく返ってきていますね
スクリーンショット 2019-05-11 12.33.47.png

tips1 レスポンスににユーザー情報も一緒に返す

レスポンスとしてアクセストークンが返ってきますが、そこにユーザー情報も付与したいという方向け
アクセストークンレスポンスはTokenViewというviewを使用しているのでそれを継承して自分でカスタマイズしましょう
以下は例です

views.py
class OAuthResponseTokenView(TokenView):
    """OAuthのトークンレスポンスにユーザー情報を追加して返す処理"""
    @method_decorator(sensitive_post_parameters("password"))
    def post(self, request, *args, **kwargs):
        url, headers, body, status = self.create_token_response(request)
        if status == 200:
            body = json.loads(body)
            access_token = body.get("access_token")
            if access_token is not None:
                token = get_access_token_model().objects.get(
                    token=access_token)
                app_authorized.send(
                    sender=self, request=request,
                    token=token)
                body['user_info'] = {
                    'id': token.user.id,
                    'username': token.user.username,
                    'email': token.user.email
                }
                body = json.dumps(body)
        response = HttpResponse(content=body, status=status)
        for k, v in headers.items():
            response[k] = v
        return response
urls.py
    path(API_ROOT + 'oauth/token/', users_views.OAuthPostToken.as_view()),
    path('oauth/token/', users_views.OAuthResponseTokenView.as_view()), # <-追加
    path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),

レスポンスにユーザー情報が追加されて返ってきます
スクリーンショット 2019-05-11 12.54.48.png

終わりに

多少荒い説明となってしまいましたが、いかがでしょうか

上記の方法でリフレッシュトークンによるアクセストークン発行も可能です

リソースサーバーに対してアクセストークンを使用した認証方法は時間があったら記事にしようと思います

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?