Генерация одиночного токена доступа с помощью Django OAuth2 Toolkit
Я использую последний Django OAuth2 Toolkit (0.10.0) с Python 2.7, Django 1.8 и Django REST Framework 3.3
При использовании grant_type=password
Я заметил странное поведение, которое каждый раз, когда пользователь запрашивает новый токен доступа:
curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/o/token/
Новый токен доступа и токен обновления созданы. Старый токен доступа и обновления все еще можно использовать до истечения времени ожидания токена!
Мои проблемы:
- Мне нужно, чтобы каждый раз, когда пользователь запрашивал новый токен доступа, старый становился недействительным, непригодным для использования и удалялся.
- Кроме того, есть способ, которым тип пароля не будет создавать токен обновления. Я не пользуюсь этим в моем приложении.
Одно решение, которое я нашел, заключается в том, что REST Framework OAuth предоставляет конфигурацию для одного токена доступа за раз. Я не хочу использовать этого провайдера, но у меня не будет выбора.
4 ответа
Если вы хотите удалить все предыдущие токены доступа перед выпуском нового, есть простое решение этой проблемы: создайте свой собственный поставщик просмотра токенов!
Код ниже, вероятно, поможет вам достичь такой функциональности:
from oauth2_provider.models import AccessToken, Application
from braces.views import CsrfExemptMixin
from oauth2_provider.views.mixins import OAuthLibMixin
from oauth2_provider.settings import oauth2_settings
class TokenView(APIView, CsrfExemptMixin, OAuthLibMixin):
permission_classes = (permissions.AllowAny,)
server_class = oauth2_settings.OAUTH2_SERVER_CLASS
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS
def post(self, request):
username = request.POST.get('username')
try:
if username is None:
raise User.DoesNotExist
AccessToken.objects.filter(user=User.objects.get(username=username), application=Application.objects.get(name="Website")).delete()
except Exception as e:
return Response(e.message,status=400)
url, headers, body, status = self.create_token_response(request)
return Response(body, status=status, headers=headers)
Часть, которую вы должны заметить, это блок Try-Except. Там мы находим токены доступа и удаляем их. Все, прежде чем мы создаем новый.
Вы можете посмотреть, как создать свой собственный провайдер, используя OAuthLib. Также это может быть полезно: TokenView в django-oauth-toolkit. Вы можете увидеть там оригинальный Apiview. Как вы сказали, вы использовали этот пакет.
Что касается refresh_token, как уже упоминалось в других ответах, вы не можете делать то, что просите. При взгляде на код oauthlib
тип grunt пароля, вы увидите, что при его инициализации refresh_token имеет значение True. Пока вы сами не измените тип Grunt, это невозможно.
Но вы можете сделать то же самое, что мы сделали выше с токенами доступа. Создайте токен, а затем удалите токен обновления.
Мне нужно, чтобы каждый раз, когда пользователь запрашивал новый токен доступа, старый становился недействительным, непригодным для использования и удалялся.
Давать новый токен, когда вы спрашиваете его, кажется ожидаемым поведением. Разве вы не можете отменить существующее, прежде чем просить новое?
Обновить
Если вы решили оставить только один токен - класс OAuth2Validator наследует OAuthLib
RequestValidator
и переопределяет метод save_bearer_token. В этом методе перед кодом, связанным с созданием экземпляра модели AccessToken и его методом.save(), вы можете запросить (аналогично этому), чтобы проверить, существует ли уже сохраненный AccessToken в БД для этого пользователя. Если найден существующий токен, его можно удалить из базы данных.Я настоятельно рекомендую сделать это изменение настраиваемым на случай, если вы передумаете в будущем (в конце концов, по таким причинам будет выпущено несколько токенов)
Более простое решение - иметь собственный класс валидатора, который, вероятно, наследует oauth2_provider.oauth2_validators.OAuth2Validator
и переопределяет save_bearer_token
, Этот новый класс должен быть дан дляOAUTH2_VALIDATOR_CLASS
вsettings.py
Кроме того, есть способ, которым тип пароля не будет создавать токен обновления. Я не пользуюсь этим в моем приложении.
Django OAuth Toolkit зависит от OAuthLib.
Делая refresh_token необязательным, сводится к create_token
метод вBearerToken
класс oAuthLib в этой строке и класс для предоставления пароля здесь. Как вы можете видеть __init__
Метод для этого класса занимает refresh_token
аргумент, который по умолчанию установлен вTrue
, Это значение используется вcreate_token_response
метод того же класса на линии
token = token_handler.create_token(request, self.refresh_token)
create_token_response
метод вOAuthLibCore
класс Django OAuth, я полагаю, называет соответствующий create_token_response
в OAuthLib. Соблюдайте использование self.server
и его инициализация в __init__
метод этого класса, который имеет только валидатор, переданный в качестве аргумента, но ничего не связанного с refresh_token
,
Сравните это с OAuthLib. Неявный тип предоставления. create_token_response
метод, который явно делает
token = token_handler.create_token(request, refresh_token=False)
не создавать refresh_token
совсем
Так что, если я что-то здесь не упустил, tldr, я не думаю, что инструментарий Django OAuth предоставляет возможность опционального refresh_token
,
Вот пример того, как сделать это напрямую:
from oauthlib.common import generate_token
from oauth2_provider.models import AccessToken, Application
from django.utils import timezone
from dateutil.relativedelta import relativedeltatok = generate_token()
tok = generate_token()
app = Application.objects.first9)
user = User.objects.first()
access_token = AccessToken.objects.create(user=user, application=app, expires=timezone.now() + relativedelta(hours=1), token=tok)
Принятый ответ по-прежнему не очищает RefreshToken. Код ниже должен отменить как токен обновления, так и токен доступа.
from oauth2_provider.models import RefreshToken
def clear_token(user):
"""
Clear all user authorized tokens.
"""
for token in RefreshToken.objects.filter(user=user, revoked__isnull=True):
token.revoke()