问题
I'm trying to put an authentication api on my django app so I can start using a Vue+Node frontend. I've read over the knox documentation, but I don't seem to see anything suggesting what the issue is. Any insight would be greatly appreciated. My suspicious are below.
What I'm doing: I was following this tutorial which suggest using knox, which seems to have nice features. When I try to curl to create a user, everything works as expected, but when I try to login (via curl), I get the following response (401) in my console:
API Call
(venv) brads-mbp:ReportVisualization brads$ curl --request POST --url http://127.0.0.1:8000/backend/auth/register/ --header 'content-type: application/json' --data '{"username": "user3", "password": "hunter2"}'
{"user":{"id":4,"username":"user3"},"token":"2b261fcdcbfff47cd8819accc2a3a08befa169c59982659a64012c680a69c3e8"}
(venv) brads-mbp:ReportVisualization brads$ curl --request POST --url http://127.0.0.1:8000/backend/auth/login/ --header 'content-type: application/json' --data '{"username": "user3","password": "hunter2"}'
{"detail":"Authentication credentials were not provided."}
Server Log
System check identified no issues (0 silenced).
December 18, 2018 - 07:47:05
Django version 2.1.3, using settings 'project.settings'
Starting ASGI/Channels version 2.1.5 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
HTTP POST /backend/auth/register/ 200 [0.13, 127.0.0.1:61332]
Unauthorized: /backend/auth/login/
HTTP POST /backend/auth/login/ 401 [0.00, 127.0.0.1:61336]
The curl login response makes me think there is something wrong with my setup of knox or django's auth. The server message seems to suggest an issue with the routing, so here is my urls:
project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.views.generic import RedirectView, TemplateView
from django.contrib.auth import views as auth_views
from .router import ROUTER
urlpatterns = [
# path('', auth_views.LoginView.as_view(template_name='registration/login.html'), name='login'),
path('admin/', admin.site.urls),
# path('favicon.ico', RedirectView.as_view(url='/static/project/images/favicon.ico')),
# path('background.jpg', RedirectView.as_view(url='/static/project/images/background.jpg')),
# path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name='logout'),
# path('home/', TemplateView.as_view(template_name="index.html")),
path('backend/', include('backend.urls')),
path('api/', include(ROUTER.urls))
]
backend/urls.py
from django.urls import path, include
from . import views, authorization_views, dash_views
urlpatterns = [
# path('filter/', views.DataFilter.as_view(), name='filter'),
# path('entry/', views.CreateDataEntryBlock.as_view(), name='entry'),
# path('home/', views.home_view, name='home'),
# path('home/', views.index, name='index'),
# path('public/', views.public),
# path('private/', views.private),
path('auth', include('knox.urls')),
path('auth/register/', authorization_views.RegistrationAPI.as_view()),
path('auth/login/', authorization_views.LoginAPI.as_view()),
]
authorization_views.py
from rest_framework import viewsets, permissions, generics
from rest_framework.response import Response
from knox.models import AuthToken
# from Domino.backend.models import User
from .serializers import CreateUserSerializer, UserSerializer, LoginUserSerializer
class RegistrationAPI(generics.GenericAPIView):
serializer_class = CreateUserSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)
})
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginUserSerializer
def post(self, request, *args, **kwargs):
import pdb; pdb.set_trace()
print("LoginAPI - post")
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)
})
backend/serializers.py
from datetime import datetime, timedelta
from rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(validated_data['username'],
None,
validated_data['password'])
return user
class LoginUserSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, data):
import pdb; pdb.set_trace()
print("LoginAPI - post")
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("Unable to log in with provided credentials.")
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
project/settings.py
import os
# import json
# from six.moves.urllib import request
# from cryptography.x509 import load_pem_x509_certificate
# from cryptography.hazmat.backends import default_backend
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = '...'
DEBUG = True
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'backend.apps.BackendConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'channels',
'widget_tweaks',
'django_filters',
'crispy_forms',
'rest_framework',
'knox',
# 'rest_framework_jwt'
'corsheaders',
]
# App settings
CORS_ORIGIN_ALLOW_ALL = True
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'project/templates'),
os.path.join(BASE_DIR, 'backend/templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'project.wsgi.application'
ASGI_APPLICATION = "project.routing.application"
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'djongo',
'NAME': 'test',
}
}
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
LOGIN_URL = ''
LOGIN_REDIRECT_URL = 'backend/home/'
STATIC_URL = '/static/'
# STATIC_ROOT = os.path.join(BASE_DIR, '../static/')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "project/static"),
os.path.join(BASE_DIR, "backend/static"),
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',),
}
Environment
(venv) brads-mbp:ReportVisualization brads$ pip list
brads$
Package Version
----------------------- ----------
asgiref 2.3.2
asn1crypto 0.24.0
astroid 2.1.0
async-timeout 3.0.1
atomicwrites 1.2.1
attrs 18.2.0
autobahn 18.11.2
Automat 0.7.0
certifi 2018.11.29
cffi 1.11.5
channels 2.1.5
chardet 3.0.4
constantly 15.1.0
cryptography 2.4.2
daphne 2.2.3
dataclasses 0.6
Django 2.1.3
django-cors-headers 2.4.0
django-crispy-forms 1.7.2
django-filter 2.0.0
django-rest-knox 3.4.0
django-widget-tweaks 1.4.3
djangorestframework 3.9.0
djangorestframework-jwt 1.11.0
djongo 1.2.30
ecdsa 0.13
future 0.17.1
hyperlink 18.0.0
idna 2.7
incremental 17.5.0
isort 4.3.4
lazy-object-proxy 1.3.1
mccabe 0.6.1
more-itertools 4.3.0
numpy 1.15.4
Pillow 5.3.0
pip 18.1
pluggy 0.8.0
py 1.7.0
pyasn1 0.4.4
pyasn1-modules 0.2.2
pycparser 2.19
PyHamcrest 1.9.0
PyJWT 1.7.1
pylint 2.2.2
pylint-django 2.0.4
pylint-plugin-utils 0.4
pymongo 3.7.2
pyOpenSSL 18.0.0
pytest 4.0.1
python-jose 3.0.1
pytz 2018.7
requests 2.20.1
rope 0.11.0
rsa 4.0
scipy 1.1.0
service-identity 18.1.0
setuptools 40.6.2
six 1.11.0
sqlparse 0.2.4
Twisted 18.9.0
txaio 18.8.1
typed-ast 1.1.0
urllib3 1.24.1
wheel 0.32.3
wrapt 1.10.11
zope.interface 4.6.0
I feel like I have to have something simple that is wrong with a setting, but I can't seem to figure out what it is. I saw some posts that claimed an issue with "Apache and mod_wsgi" which isn't an issue for me as I am running local atm, however I'm running channels so I wonder if that has any impact, or maybe it has to do with the auth password validators in settings?
回答1:
I ended up getting it work by dropping knox and just using basic auth. I did make other changes like using rest_framework.authentication.TokenAuthentication and rest_framework.permissions.IsAuthenticated in my REST_FRAMEWORK setting and including permission_classes = (AllowAny, ) in my registration and login APIViews. Pretty basic stuff, but I think this should work for what I need. I don't know what I was doing wrong with knox but it seems more trouble that it is worth.
回答2:
AuthToken.objects.create(user) returns two values - token instance and token key
回答3:
Short answer, go to settings.py and edit DEFAULT_AUTHENTICATION_CLASSES :
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication' ,
'knox.auth.TokenAuthentication',),
}
The issue here is that DEFAULT_AUTHENTICATION_CLASSES is set to knox.auth.TokenAuthentication, this means that all views will have this as their default auth class thus needs a token including the knox login view itself.
By adding 'rest_framework.authentication.BasicAuthentication' , auth classes will be tested in sequence until one of them works, so for login, you can now do as an example:
curl --location --request POST 'http://127.0.0.1:8000/api/v1/auth/login/' \ --header 'Authorization: Basic username:password'
来源:https://stackoverflow.com/questions/53828599/how-to-authorize-django-rest-knox-login-path-without-401-error