Процесс разработки приложения под Google App Engine замечателен во многих смыслах: он интересен, а приложение легко масштабируется и доступно огромному числу пользователей по всему миру. Если у вас есть аккаунт Gmail, вы можете пройти авторизацию на сайте, использующем App Engine.
А что можно сделать, чтобы не пускать кого попало в наше приложение? К примеру, представьте, что вы создали небольшой сайт, который хотите открыть только для членов семьи и своих друзей. Каким образом можно предотвратить доступ для всех других?
Одним из решений проблемы является составление списка разрешенных пользователей (либо получения его из базы данных). Это годится в том случае, если вы знаете кто же допущен к сайту, но к сожалению требует постоянного изменения этого списка и поддержания его в актуальном состоянии. Другим интересным методом является использование системы инвайтов (приглашений), как той, какую на начальном этапе имел Gmail, но это тоже означает, что-то должен управлять этими приглашениями. Следующий пример показывает работу золотой середины между этими описанными методами — технология, названная HMAC, которая позволяет убедиться, конкретный аккаунт Google имеет право доступа.
Следующий код создает криптографический хэш для заданного имени порльзователя. Он использует удобный метод из модуля cryptutil, который сам является частью примера кода OpenID. В процессе работы приложение оперирует секретным ключом, который ни в коем случае не должен быть опубликован. К конечному пользователю передается только сгенерированная хэш-строка:
import base64
from openid import cryptutil
def sign(username):
# В реальной жизни выберите лучший секретный ключ!
secret='superSecretKey'
return base64.encodestring(
cryptutil.hmacSha1(secret, username))
Так как эта проверка работает на практике? Давайте посмотрим на простой обработчик запросов, который будет использовать ее:
import cgi
import os
import urllib
import wsgiref.handlers
from google.appengine.api import users
from google.appengine.ext import webapp
class MainPage(webapp.RequestHandler):
def isValid(self):
"""Определение, что заданный запрос валиден.
Для выполнения этих критериев, текущий пользователь должен
быть авторизован и быть либо администратором, либо передать
в запросе зашифрованный параметр, совпадающий с его именем пользователя.
"""
user = users.get_current_user()
if not user:
return False
if not users.is_current_user_admin():
expected_signature = sign(user.email().lower())
given_signature = self.request.get('signature')
if not given_signature == expected_signature:
return False
return True
Наш метод выполняет проверку, имеет ли данный запрос параметр с подписью. Если да, он сравнивает его с хэшем, вычисленным из адреса электронной почты текущего пользователя. И только в том случае, если оба этих ключа совпадают, пользователю предоставляется доступ.
Следующие методы get и post показывают работу простейшего приложения, отображающего важное сообщение только для приглашенных пользователей. Введя адрес электронной почты другого пользователя, мы позволяем системе создать подписанный URL, который затем будет передан приглашаемому пользователю:
def get(self):
"""Отображение сообщения только для приглашенных пользователей."""
# Требуется авторизация
user = users.get_current_user()
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
# Если пользователь не администратор, проверить подпись
if not self.isValid():
self.error(403)
return
# Ok, пользователь допущен к просмотру страницы!
# В реальной жизни мы воспользуемся в этом месте
# шаблоном
self.response.out.write("""
<html><body>
<h1>Hello, %s</h1>
<p>Ниже секретное сообщение, которое вы ищете:
<b>APP ENGINE РУЛИТ!!!</b>
</p>
<form method="POST">
Передать сообщение другу
<input name="invitee"/>
<input type="submit"/></form>
<p><a href="%s">Log out</a></p>
</body></html
""" % (cgi.escape(user.nickname()),
users.create_logout_url(self.request.uri)))
def post(self):
"""Создание строки URL для последующей отправки приглашенному."""
# Если пользователь не администратор, проверить подпись
if not self.isValid():
self.error(403)
return
# Получить имя invitee
invitee = self.request.get('invitee')
if not invitee:
invitee = 'anonymnous'
invitee = invitee.lower()
# подписать инвайт и вывести url
signature = sign(invitee)
self.response.out.write("""
<html><body>
URL для друга:
http://%s?signature=%s</body></html>""" % (
os.environ['SERVER_NAME'],
urllib.quote(signature, '')))
Несмотря на то, что это выглядит игрушечным приложением, этот пример показывает практический подход, который может быть использован для современных веб-приложений в ответ на вопрос: как можно проверить, что запрос пришел из надежного источника. Для более комплексного алгоритма обратитесь к спецификации OAuth, в которой описываются подходы использования подписи веб-запросов с помощью HMAC.