django-tastypieでApiKey認証をかける

先日django-tastypieで作ったAPIBasic認証をかける方法を試してみました。


今日は自動的に生成されるApiKeyを使うApiKey認証というのを試してみたいと思います。
Basic認証ではユーザーのパスワードが必要ですが、それが好ましくない場合はこっちを使うみたいです。


ApiKey認証をかけるにはResourceのMetaクラスにauthentication = ApiKeyAuthentication()を記述します

resources.py

from tastypie.authentication import BasicAuthentication
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from app.models import Book

class BookResource(ModelResource):
    class Meta:
        queryset = Book.objects.all()
        # authentication = BasicAuthentication() 
        authentication = ApiKeyAuthentication()
        authorization = Authorization()


Basic認証だとこれだけで使えたんですが、ApiKey認証の場合はもう一手間必要です。


まずUser毎にKeyを保存するためのテーブルが必要です。
tastypie/models.pyにApiKeyというモデルが定義されてます。
INSTALLED_APPSにtastypieを追加した状態でsyncdbをすればテーブルが作成されます

settings.py

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'app',
    'tastypie',
)


ApiKeyのレコードはUserのレコードを作成するときにsignalsを使って作成できます。
create_api_key関数がtastypie.modelsに用意されてますので、それが使えます。


models.py

# coding=utf-8
from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=255)
    pub_date = models.DateTimeField(auto_now_add=True)

# 下記追加
from django.contrib.auth.models import User
from tastypie.models import create_api_key
models.signals.post_save.connect(create_api_key, sender=User)


manage.py createsuperuserでユーザーを作ってみると、ApiKeyのレコードが作成されているのがわかります

>>> from tastypie.models import ApiKey
>>> ApiKey.objects.all()
Out[5]: [<ApiKey: 430b1c2faed2328fd7a4133edefea90a4f06324b for user2>]


このapiキーとユーザー名をGETかPOSTのパラメータに指定してapiにアクセスできるようになります。
requestsを使ったアクセス方法はこんな感じになります

# coding=utf-8
import urllib
import requests
import json

auth_params = {
    "username": u"user2",
    "api_key": u"430b1c2faed2328fd7a4133edefea90a4f06324b"
    }
auth_query = urllib.urlencode(auth_params)

# データを全部削除
r = requests.delete('http://localhost:8000/api/book/?' + auth_query)
assert(r.status_code == 204)

# データが全て消えていることを確認
r = requests.get('http://localhost:8000/api/book/?' + auth_query)
assert(json.loads(r.text)["meta"]["total_count"] == 0)

# 登録
payload = {'title': u'Python Cook Book', 'pub_date': "2012/9/25"}
r = requests.post('http://localhost:8000/api/book/?' + auth_query,
                  data=json.dumps(payload),
                  headers={"Content-Type": "application/json"} )
assert(r.status_code == 201)

# 取得
r = requests.get('http://localhost:8000/api/book/?' + auth_query)
res = json.loads(r.text)
assert(len(res["objects"]) == 1)
assert(res["objects"][0]["title"] == u"Python Cook Book")
assert(res["objects"][0]["id"] == "1")

# idを指定して取得
r = requests.get('http://localhost:8000/api/book/1?' + auth_query)
res = json.loads(r.text)
assert(res["id"] == "1")
assert(res["title"] == u"Python Cook Book")

# 更新
payload = {'id': 1,
           'title': u'Python Cook Book 2nd Edition',
           'pub_date': "2013/1/25"}
r = requests.post('http://localhost:8000/api/book/?' + auth_query,
                  data=json.dumps(payload),
                  headers={"Content-Type": "application/json"})
assert(r.status_code == 201)


# 更新内容を確認
r = requests.get('http://localhost:8000/api/book/1?' + auth_query)
res = json.loads(r.text)
assert(res["id"] == "1")
assert(res["title"] == u"Python Cook Book 2nd Edition")


# idを指定して削除
r = requests.delete('http://localhost:8000/api/book/1?' + auth_query)
assert(r.status_code == 204)

# データが消えていることを確認
r = requests.get('http://localhost:8000/api/book/1?' + auth_query)
assert(r.status_code == 404)


POSTの場合、データはjsonで渡してますが、jsonにusernameやapi_keyを設定しても401になります。
あくまでGETかPOSTでusername、api_keyという名前で渡さないといけない。
ですので、上記の例では全部クエリストリングとして渡してます。