認証とセキュリティ¶
ユーザー認証¶
現在認証されているユーザーは、すべてのリクエストハンドラーで self.current_user
として、すべてのテンプレートで current_user
として使用できます。デフォルトでは、current_user
は None
です。
アプリケーションでユーザー認証を実装するには、たとえば Cookie の値に基づいて現在のユーザーを決定するために、リクエストハンドラーの get_current_user()
メソッドをオーバーライドする必要があります。これは、ユーザーがニックネームを指定するだけでアプリケーションにログインできる例です。ニックネームはCookieに保存されます。
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_signed_cookie("user")
class MainHandler(BaseHandler):
def get(self):
if not self.current_user:
self.redirect("/login")
return
name = tornado.escape.xhtml_escape(self.current_user)
self.write("Hello, " + name)
class LoginHandler(BaseHandler):
def get(self):
self.write('<html><body><form action="/login" method="post">'
'Name: <input type="text" name="name">'
'<input type="submit" value="Sign in">'
'</form></body></html>')
def post(self):
self.set_signed_cookie("user", self.get_argument("name"))
self.redirect("/")
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")
Python デコレーター tornado.web.authenticated
を使用して、ユーザーがログインしていることを要求できます。このデコレーターが付いたメソッドにリクエストが行われ、ユーザーがログインしていない場合、login_url
(別のアプリケーション設定)にリダイレクトされます。上記の例は書き直すことができます。
class MainHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
name = tornado.escape.xhtml_escape(self.current_user)
self.write("Hello, " + name)
settings = {
"cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
"login_url": "/login",
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
], **settings)
post()
メソッドを authenticated
デコレーターでデコレートし、ユーザーがログインしていない場合、サーバーは 403
レスポンスを送信します。@authenticated
デコレーターは単に if not self.current_user: self.redirect()
の略記であり、ブラウザ以外のログインスキームには適していない可能性があります。
認証を使用する完全な例(およびユーザーデータを PostgreSQL データベースに保存する)については、Tornado ブログの例アプリケーション をご覧ください。
サードパーティ認証¶
tornado.auth
モジュールは、Google/Gmail、Facebook、Twitter、FriendFeed など、Web 上で最も人気のあるサイトのいくつかについて、認証と承認のプロトコルを実装しています。このモジュールには、これらのサイトを介してユーザーをログインさせるメソッドと、該当する場合はサービスへのアクセスを承認するメソッド(ユーザーのアドレス帳をダウンロードしたり、代わりに Twitter メッセージを公開したりする場合など)が含まれています。
これは、認証に Google を使用し、後でアクセスするために Google の資格情報を Cookie に保存するハンドラーの例です。
class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,
tornado.auth.GoogleOAuth2Mixin):
async def get(self):
if self.get_argument('code', False):
user = await self.get_authenticated_user(
redirect_uri='http://your.site.com/auth/google',
code=self.get_argument('code'))
# Save the user with e.g. set_signed_cookie
else:
await self.authorize_redirect(
redirect_uri='http://your.site.com/auth/google',
client_id=self.settings['google_oauth']['key'],
scope=['profile', 'email'],
response_type='code',
extra_params={'approval_prompt': 'auto'})
詳細は、tornado.auth
モジュールのドキュメントを参照してください。
クロスサイトリクエストフォージェリ保護¶
クロスサイトリクエストフォージェリ(XSRF)は、パーソナライズされた Web アプリケーションの一般的な問題です。
XSRF を防ぐために一般的に受け入れられている解決策は、予測不可能な値で各ユーザーに Cookie を設定し、その値をサイト上のすべてのフォーム送信に追加の引数として含めることです。Cookie とフォーム送信の値が一致しない場合、リクエストは偽造されている可能性が高いです。
Tornado には、組み込みの XSRF 保護が備わっています。サイトに含めるには、アプリケーション設定 xsrf_cookies
を含めます。
settings = {
"cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
"login_url": "/login",
"xsrf_cookies": True,
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
], **settings)
xsrf_cookies
が設定されている場合、Tornado Web アプリケーションはすべてのユーザーに対して _xsrf
Cookie を設定し、正しい _xsrf
値を含まないすべての POST
、PUT
、および DELETE
リクエストを拒否します。この設定をオンにした場合、POST
で送信するすべてのフォームにこのフィールドを組み込む必要があります。これは、すべてのテンプレートで使用できる特別な UIModule
xsrf_form_html()
を使用して実行できます。
<form action="/new_message" method="post">
{% module xsrf_form_html() %}
<input type="text" name="message"/>
<input type="submit" value="Post"/>
</form>
AJAX POST
リクエストを送信する場合、各リクエストに _xsrf
値を含めるように JavaScript を調整する必要もあります。これは、FriendFeed で AJAX POST
リクエストに使用している jQuery 関数であり、すべてのリクエストに _xsrf
値を自動的に追加します。
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
jQuery.postJSON = function(url, args, callback) {
args._xsrf = getCookie("_xsrf");
$.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
success: function(response) {
callback(eval("(" + response + ")"));
}});
};
PUT
および DELETE
リクエスト(フォームエンコードされた引数を使用しない POST
リクエストも同様)の場合、XSRF トークンは X-XSRFToken
という名前の HTTP ヘッダーを介して渡すこともできます。XSRF Cookie は通常、xsrf_form_html
が使用されるときに設定されますが、通常のフォームを使用しない純粋な JavaScript アプリケーションでは、self.xsrf_token
に手動でアクセスする必要がある場合があります(プロパティを読み取るだけで、Cookie が副作用として設定されます)。
ハンドラーごとに XSRF の動作をカスタマイズする必要がある場合は、RequestHandler.check_xsrf_cookie()
をオーバーライドできます。たとえば、認証に Cookie を使用しない API がある場合、check_xsrf_cookie()
を何も実行しないようにして、XSRF 保護を無効にすることができます。ただし、Cookie ベースと Cookie ベース以外の認証の両方をサポートする場合は、現在のリクエストが Cookie で認証されているときは常に XSRF 保護を使用することが重要です。
DNS リバインディング¶
DNSリバインディングは、同一オリジンポリシーを回避し、外部サイトがプライベートネットワーク上のリソースにアクセスすることを可能にする攻撃です。この攻撃は、(短いTTLを持つ)DNS名を使用し、攻撃者によって制御されるIPアドレスと、被害者によって制御されるIPアドレス(多くの場合、127.0.0.1
や192.168.1.1
などの推測可能なプライベートIPアドレス)との間で交互に切り替わります。
TLSを使用するアプリケーションは、この攻撃に対して脆弱ではありません(ブラウザは証明書ミスマッチの警告を表示し、ターゲットサイトへの自動アクセスをブロックするためです)。
TLSを使用できず、ネットワークレベルのアクセス制御に依存するアプリケーション(たとえば、127.0.0.1
上のサーバーはローカルマシンからのみアクセスできると仮定する)は、Host
HTTPヘッダーを検証することでDNSリバインディングから保護する必要があります。これは、HostMatches
ルーターまたはApplication.add_handlers
の最初の引数に、制限的なホスト名パターンを渡すことを意味します。
# BAD: uses a default host pattern of r'.*'
app = Application([('/foo', FooHandler)])
# GOOD: only matches localhost or its ip address.
app = Application()
app.add_handlers(r'(localhost|127\.0\.0\.1)',
[('/foo', FooHandler)])
# GOOD: same as previous example using tornado.routing.
app = Application([
(HostMatches(r'(localhost|127\.0\.0\.1)'),
[('/foo', FooHandler)]),
])
さらに、Application
のdefault_host
引数とDefaultHostMatches
ルーターは、DNSリバインディングに対して脆弱となる可能性のあるアプリケーションでは使用しないでください。これは、ワイルドカードホストパターンと同様の効果を持つためです。