How to authorize django-rest-knox login path without 401 error?

南楼画角 提交于 2020-07-30 08:04:42

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!