http://qiita.com/seizans/items/05a909960820dd92a80a
API 作成に django-rest-framework を使います。
JWT でトークンベースの認証のために django-rest-framework-jwt というプラグインを使います。
django-rest-framework を使う場合の認証情報の保持には選択肢があり、JWT はその1つです。
JWT はトークンベースの認証で、トークンを永続化する必要が無いのが楽です。
ブラウザでの webアプリなら session_id を Cookie で持つことで認証情報を保持できますが、
その他のクライアント(iOSアプリなど)で使う API では認証情報をトークンなどで保持する必要があります。
略称
JW: JSON Web
- JWT: Token
- JWS: Signature
- JWE: Encryption
django-rest-framework-jwt の使い方
省略。README を読めばすぐわかる。
理屈
- ログインAPI は id/pass を受けて、これが正しければ、トークンを発行する
-
トークンは次の情報を(base64encodeされた文字列として)含む:
- JWTヘッダ: 続く中身が JWT 形式だということを示すなど
- クレームセット(要は中身): user_id, expire を含む
- HMAC値: 上2つを連結したものの HMAC値 (これで上2つがクライアント側で書き換えられてないか検証している)
- APIへのリクエストにはトークンを含める。これをサーバー側でデコードして user_id を得て認証となる (中身が改ざんされていないことも検証している)
設定すること
- トークンの有効期間 (無期限も可能)
運用に関して
- サーバーが HMAC 生成に使うシークレットキー (デフォルトは settings.SECRET_KEY) の運用: これが漏れると「なりすまし」攻撃される
正常系フロー
- ログインAPI に POST で id/pass を送り、JWTトークンをレスポンスで受け取る
- 認証が必要なAPI へのリクエストに "Authorization: JWT [トークン中身]" ヘッダを含める
例
- ログインAPI への POST
$ http POST :8000/api-token-auth/ username=ichiro@example.com1 password=password -vvv POST /api-token-auth/ HTTP/1.1 Accept: application/json Accept-Encoding: gzip, deflate, compress Content-Length: 59 Content-Type: application/json; charset=utf-8 Host: localhost:8000 User-Agent: HTTPie/0.8.0 { "password": "password", "username": "ichiro@example.com1" } HTTP/1.0 200 OK Allow: POST, OPTIONS Content-Type: application/json Date: Thu, 05 Jun 2014 07:30:40 GMT Server: WSGIServer/0.1 Python/2.7.5 X-Frame-Options: SAMEORIGIN { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJ1c2VyX2lkIjoxLCJlbWFpbCI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJleHAiOjE0MDE5NTM3NDB9.VITFxMzF8RUgUVwKoTcIFcZS9pFGdCkLqhWNzbBoscY" }
- 認証が必要なAPI への GET
$ http GET :8000/api/snippets/ Authorization:"JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJ1c2VyX2lkIjoxLCJlbWFpbCI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJleHAiOjE0MDE5NTUxNzJ9.tawmWS4X0WlOaQ36_K-z9UwA5sBzGVPHOi_uGRPTgwk" -vvv ⏎ GET /api/snippets/ HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, compress Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJ1c2VyX2lkIjoxLCJlbWFpbCI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJleHAiOjE0MDE5NTUxNzJ9.tawmWS4X0WlOaQ36_K-z9UwA5sBzGVPHOi_uGRPTgwk Host: localhost:8000 User-Agent: HTTPie/0.8.0 HTTP/1.0 200 OK Allow: GET, POST, HEAD, OPTIONS Content-Type: application/json Date: Thu, 05 Jun 2014 07:55:28 GMT Server: WSGIServer/0.1 Python/2.7.5 Vary: Accept X-Frame-Options: SAMEORIGIN { "count": 0, "next": null, "previous": null, "results": [] }
認証失敗パターン (401 UNAUTHORIZED)
- トークンが期限切れ
http GET :8000/api/snippets/ Authorization:"JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJ1c2VyX2lkIjoxLCJlbWFpbCI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJleHAiOjE0MDE5NTM3NDB9.VITFxMzF8RUgUVwKoTcIFcZS9pFGdCkLqhWNzbBoscY" -vvv GET /api/snippets/ HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, compress Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJ1c2VyX2lkIjoxLCJlbWFpbCI6ImljaGlyb0BleGFtcGxlLmNvbTEiLCJleHAiOjE0MDE5NTM3NDB9.VITFxMzF8RUgUVwKoTcIFcZS9pFGdCkLqhWNzbBoscY Host: localhost:8000 User-Agent: HTTPie/0.8.0 HTTP/1.0 401 UNAUTHORIZED Allow: GET, POST, HEAD, OPTIONS Content-Type: application/json Date: Thu, 05 Jun 2014 07:54:49 GMT Server: WSGIServer/0.1 Python/2.7.5 Vary: Accept WWW-Authenticate: JWT realm="api" X-Frame-Options: SAMEORIGIN { "detail": "Signature has expired." }
-
トークンの形式がおかしい:
{ "detail": "Error decoding signature." }
- 認証されているけど権限を持っていない: (あとで追記する)
参考
-
django-rest-framework-jwt: https://github.com/GetBlimp/django-rest-framework-jwt
-
JWT: http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20
-
JWT 日本語(上のと版が違うので注意): http://openid-foundation-japan.github.io/draft-ietf-oauth-json-web-token-11.ja.html
-
日本語での JWT の解説記事: http://hiyosi.tumblr.com/post/70073770678/jwt
-
Cookie VS Token: https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/
-
JOSE まとめ: http://hde-advent-2015.hatenadiary.jp/entry/2015/12/02/095643