Как я могу использовать Django OAuth Toolkit с социальной аутентификацией Python?

Я создаю API, используя Django Rest Framework. Позже этот API предполагается использовать на устройствах iOS и Android. Я хочу, чтобы мои пользователи могли подписаться на OAuth2-провайдеров, таких как Facebook и Google. В этом случае им вообще не нужно создавать учетную запись на моей платформе. Но пользователи также должны иметь возможность зарегистрироваться, когда у меня нет учетной записи Facebook/Google, для которой я использую django-OAuth-toolkit, поэтому у меня есть собственный OAuth2-provider.

для внешних провайдеров я использование python-social-auth, который отлично работает и автоматически создает пользовательские объекты.

Я хочу, чтобы клиенты аутентифицировались с помощью токенов на предъявителя, которые отлично работают для пользователей, подписавшихся с моим провайдером (django-OAuth-toolkit предоставляет схему аутентификации и классы разрешений для Django Rest Framework).
однако python-social-auth реализует аутентификацию только на основе сеанса, поэтому нет простого способа сделать аутентифицированные запросы API от имени пользователей это зарегистрировано внешним поставщиком oauth2.

если я использую access_token, который был сгенерирован django-OAuth-toolkit, выполнение такого запроса работает:

curl -v -H "Authorization: Bearer <token_generated_by_django-oauth-toolkit>" http://localhost:8000/api/

однако следующее не работает, так как нет соответствующей схемы аутентификации для Django Rest Framework, а AUTHENTICATION_BACKENDS, предоставляемые python-social-auth, работают только для аутентификации на основе сеанса:

curl -v -H "Authorization: Bearer <token_stored_by_python-social-auth>" http://localhost:8000/api/

использование просматриваемого API, предоставляемого Django REST Framework после аутентификации с помощью python-social-auth работает отлично, только вызовы API без cookie сеанса не работают.

мне интересно, какой лучший подход для этой проблемы. Как я вижу, у меня есть в основном два варианта:

A: когда пользователь подписывается на внешний поставщик oauth2 (обрабатывается python-social-auth), подключитесь к процессу создания oauth2_provider.модели.AccessToken и продолжать использовать 'oauth2_provider.ext.rest_framework.OAuth2Authentication', теперь аутентификация также пользователей, которые зарегистрирован у внешнего провайдера. Этот подход предлагается здесь: https://groups.google.com/d/msg/django-rest-framework/ACKx1kY7kZM/YPWFA2DP9LwJ

B: используйте python-social-auth для аутентификации запроса API. Я мог бы получить своих собственных пользователей в python-social-auth, написав пользовательский бэкэнд и используя register_by_access_token. Однако, поскольку вызовы API не могут использовать сеансы Django, это означает, что мне придется написать схему аутентификации для Django Rest Фреймворк, который использует данные, хранящиеся в python-social-auth. Несколько советов о том, как это сделать можно найти здесь:
http://psa.matiasaguirre.net/docs/use_cases.html#signup-by-oauth-access-token
http://blog.wizer.fr/2013/11/angularjs-facebook-with-a-django-rest-api/
http://cbdev.blogspot.it/2014/02/facebook-login-with-angularjs-django.html
Однако, как я понимаю, python-social-auth проверяет только токен при входе в систему и полагается на сеанс Django после этого. Это означало бы, что мне придется найти способ предотвратить python-social-auth от выполнения всего OAuth2-потока для каждого запроса API без состояния и, скорее, проверить данные, хранящиеся в БД, которые на самом деле не оптимизированы для запросов, поскольку они хранятся как JSON (я мог бы использовать UserSocialAuth.объекты.get (extra_data__contains=) хотя).
Мне также нужно будет позаботиться о проверке областей маркера доступа и использовать их для проверьте разрешения, что-то django-OAuth-toolkit уже делает (TokenHasScope, required_scopes и т. д.).

на данный момент я склоняюсь к использованию опции A, так как django-OAuth-toolkit обеспечивает хорошую интеграцию с Django Rest Framework, и я получаю все, что мне нужно, из коробки. Единственным недостатком является то, что мне нужно "ввести" access_tokens, извлеченные python-social-auth в модель AccessToken django-oauth-toolkit, которая чувствует себя неправильно, но, вероятно, будет намного проще подход.

есть ли у кого-нибудь возражения против этого или, может быть, он решил ту же проблему по-другому? Я упускаю что-то очевидное и делаю свою жизнь сложнее, чем необходимо? Если кто-то уже интегрировал django-oauth-toolkit с python-social-auth и внешними поставщиками oauth2, я был бы очень благодарен за некоторые указатели или мнения.

3 ответов


большая трудность в реализации OAuth сводится к пониманию того, как должен работать поток авторизации. Это в основном потому, что это "отправная точка" для входа в систему, и при работе с сторонним бэкэндом (используя что-то вроде социальной аутентификации Python) вы на самом деле делать это дважды: один раз для вашего API и один раз для стороннего API.

авторизация запросов с помощью API и стороннего бэкэнда

в процесс аутентификации, который вам нужен, проходит:

Sequence diagram for option A

Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Facebook : User signs in
Facebook -> Django Login : User authorizes your API
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app

Я использую "Facebook" в качестве стороннего бэкэнда здесь, но процесс одинаков для любого бэкэнда.

С точки зрения вашего мобильного приложения, вы только перенаправление на /authorize url, предоставленный Django OAuth Toolkit. Оттуда мобильное приложение ждет, пока не будет достигнут url обратного вызова, как и в стандарте Поток OAuth авторизации. Почти все остальное (логин Django, социальный логин и т. д.) обрабатывается либо Django OAuth Toolkit, либо Python Social Auth в фоновом режиме.

это также будет совместимо с почти любыми библиотеками OAuth, которые вы используете, и поток авторизации будет работать одинаково независимо от того, какой сторонний сервер используется. Он даже будет обрабатывать (общий) случай, когда вам нужно иметь возможность поддерживать бэкэнд аутентификации Django (электронная почта/имя пользователя и пароль) как а также сторонний логин.

Option A without a third-party backend

Mobile App -> Your API : Authorization redirect
Your API -> Django Login : Displays login page
Django Login -> Your API : User signs in
Your API -> Mobile App : User authorizes mobile app

также важно отметить, что мобильное приложение (которое может быть любым клиентом OAuth) никогда не получает токены Facebook/сторонних OAuth. Это невероятно важно, так как ваш API действует как посредник между клиентом OAuth и социальными учетными записями пользователя.

Sequence diagram with your API as the gatekeeper

Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives OAuth token
Mobile App -> Your API : Requests the display name
Your API -> Facebook : Requests the full name
Facebook -> Your API : Sends back the full name
Your API -> Mobile App : Send back a display name

в противном случае Клиент OAuth сможет обойти ваш API и сделать запросы от имени для сторонних API.

Sequence diagram for bypassing your API

Mobile App -> Your API : Authorization redirect
Your API -> Mobile App : Receives Facebook token
Mobile App -> Facebook : Requests all of the followers
Facebook -> Mobile App : Sends any requested data

Вы заметите, что в этот момент вы потеряли бы весь контроль над сторонними токенами. Это особенно опасно, потому что большинство токенов могут получить доступ к широкому спектру данных, что открывает дверь для злоупотреблений и в конечном итоге идет вниз под именем. Более вероятный, те, кто вошел в ваш API / веб-сайт, не намеревались делиться своей социальной информацией с клиентом OAuth и вместо этого ожидали, что вы сохраните эту информацию частной (насколько это возможно), но вместо этого вы подвергаете эту информацию всем.

аутентификация запросов к вашему API

когда мобильное приложение затем использует ваш токен OAuth делать запросы на ваш API все проверки подлинности происходит через Django OAuth Toolkit (или ваш поставщик OAuth) в фоновом режиме. все, что вы видите, что есть User связанные с вашим запросом.

How OAuth tokens are validated

Mobile App -> Your API : Sends request with OAuth token
Your API -> Django OAuth Toolkit : Verifies the token
Django OAuth Toolkit -> Your API : Returns the user who is authenticated
Your API -> Mobile App : Sends requested data back

это важно, потому что после этапа авторизации не должно иметь значения, идет ли пользователь из системы аутентификации Facebook или Django. Ваш API просто нуждается в User для работы, и ваш провайдер OAuth должен иметь возможность обрабатывать аутентификацию и проверку токена.

это не сильно отличается от того, как Django Rest framework аутентифицирует пользователя при использовании аутентификации с поддержкой сеанса.

Sequence diagram for authenticating using sessions

Web Browser -> Your API : Sends session cookie
Your API -> Django : Verifies session token
Django -> Your API : Returns session data
Your API -> Django : Verifies the user session
Django -> Your API : Returns the logged in user
Your API -> Web Browser : Returns the requested data

опять все это обрабатывается Django OAuth Toolkit и не требует дополнительной работы по реализации.

работа с собственным SDK

в большинстве случаев, вы собираетесь аутентификация пользователя через собственный веб-сайт и использование социальной аутентификации Python для обработки всего. Но одно заметное исключение - при использовании собственного SDK, как аутентификация и авторизация обрабатываются через родную систему, что означает вы полностью обходите свой API. Это отлично подходит для приложений, которые должны войти в систему с третьей стороной, или приложений, которые не используют ваш API вообще, но это кошмар, когда оба приходят вместе!--29-->.

это так ваш сервер не может проверить логин и предположим, что логин действителен и подлинный, что означает, что он обходит любую безопасность, которую дает вам социальная аутентификация Python.

Using a native SDK can cause issues

Mobile App -> Facebook SDK : Opens the authorization prompt
Facebook SDK -> Mobile App : Gets the Facebook token
Mobile App -> Your API : Sends the Facebook token for authorization
Your API -> Django Login : Tries to validate the token
Django Login -> Your API : Returns a matching user
Your API -> Mobile App : Sends back an OAuth token for the user

вы заметите, что это пропускает ваш API во время фазы аутентификации, а затем заставляет ваш API делать предположения о переданном токене в. Но есть определенно случаи, когда этот риск может быть стоит, поэтому вы должны оценить это, прежде чем выбрасывать его. Это компромисс между быстрые и собственные логины для вашего пользователя и потенциально обработка плохих или вредоносных токенов.


я решил это, используя Ваш вариант A.

то, что я делаю, это регистрация пользователей, которые используют третью сторону для регистрации их стороннего маркера доступа.

url(r'^register-by-token/(?P<backend>[^/]+)/$',
    views.register_by_access_token),

таким образом, я могу выдать запрос GET, как этот:

GET http://localhost:8000/register-by-token/facebook/?access_token=123456

и register_by_access_token вызывается. request.backend.do_auth запросит у поставщика информацию о пользователе из токена и магически зарегистрирует учетную запись Пользователя с информацией или войдите в пользователя, если он уже зарегистрирован.

затем я создаю токен вручную и возвращаю его как JSON, чтобы позволить клиенту запросить мой API.

from oauthlib.common import generate_token
...
@psa('social:complete')
def register_by_access_token(request, backend):
    # This view expects an access_token GET parameter, if it's needed,
    # request.backend and request.strategy will be loaded with the current
    # backend and strategy.
    third_party_token = request.GET.get('access_token')
    user = request.backend.do_auth(third_party_token)

    if user:
        login(request, user)

        # We get our app!   
        app = Application.objects.get(name="myapp")

        # We delete the old token
        try:
            old = AccessToken.objects.get(user=user, application=app)
        except:
            pass
        else:
            old.delete()

        # We create a new one
        my_token = generate_token()

        # We create the access token 
        # (we could create a refresh token too the same way) 
        AccessToken.objects.create(user=user,
                                   application=app,
                                   expires=now() + timedelta(days=365),
                                   token=my_token)

        return "OK" # you can return your token as JSON here

    else:
        return "ERROR"

Я просто не уверен в том, как я генерирую токен, это хорошая практика? Ну, в то же время, это работает!!


может быть django-rest-framework-социальный-oauth2 это то, что вы ищете. Этот пакет зависит от python-social-auth и django-oauth-toolkit, который вы уже используете. Я быстро просмотрел документацию, и, похоже, реализовал именно то, что вы пытаетесь сделать.