Почему POST-запрос к /api/posts/ возвращает 401 Unauthorized в Django DRF, несмотря на правильный токен в заголовках Axios?
Проблема:
У меня возникла проблема с ответом 401 Unauthorized
при выполнении POST-запроса к /api/posts/
в моем бэкенде Django Rest Framework (DRF) с фронтенда React. Экземпляр Axios в React использует перехватчик для добавления токена Bearer (хранящегося в localStorage
) в заголовок Authorization, но запрос все равно не проходит.
В представлении DRF используется generics.ListCreateAPIView
и требуется аутентификация с помощью permissions.IsAuthenticated
. POST-запрос не выполняется, хотя токен присутствует в заголовках. Я проверил конфигурации фронтенда и бэкенда, но проблема сохраняется.
В качестве url-шаблона используется posts/
. Но из фронтенда все url-шаблоны для api (Django app
) могут быть доступны только через /api/urlpattern/
.
Django DRF Backend Code
Когда я использую url-шаблон из проекта urls.py, я могу создать пост -
python
from django.contrib import admin
from django.urls import path, include
from api.views import CreateUserView, ProfileView, PostListCreateView
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('api/user/register/', CreateUserView.as_view(), name='register'),
path('api/token/', TokenObtainPairView.as_view(), name='get_token'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='refresh'),
path('api/posts/', PostListCreateView.as_view(), name='post-list'),
path('api_auth/', include('rest_framework.urls')),
path('api/', include('api.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Но когда я использую urls.py для api, он выдает 405 METHOD "POST" not allowed.
python
from django.urls import path
from . import views
urlpatterns = [
path('', views.WelcomeView.as_view(), name='welcome'),
path('home/', views.HomeView.as_view(), name='home'),
path('<str:username>/update-profile/', views.UpdateProfileView.as_view(), name='update-profile'),
path('<str:username>/', views.ProfileView.as_view(), name="profile"),
path('posts/', views.PostListCreateView.as_view(), name='post-list'),
path('posts/<int:pk>/', views.PostDetailView.as_view(), name='post-detail'),
path('posts/<int:post_pk>/likes/', views.LikesListCreateView.as_view(), name='like-list'),
path('likes/<int:pk>/', views.LikesDetailView.as_view(), name='like-detail'),
path('posts/<int:post_pk>/comments/', views.CommentListCreateView.as_view(), name='comment-list'),
path('comments/<int:pk>/', views.CommentDetailView.as_view(), name='comment-detail'),
]
models.py
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
body = models.TextField(max_length=240)
media = models.ImageField(null=True, blank=True)
created_at = models.DateTimeField(default=timezone.now)
def number_of_likes(self):
return self.likes.count()
def number_of_comments(self):
return self.comments.count()
def get_created_at_in_user_timezone(self, author):
user_timezone = pytz.timezone(author.profile.timezone)
return self.created_at.astimezone(user_timezone)
class PostSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField(source='author.username', read_only=True)
number_of_likes = serializers.SerializerMethodField()
number_of_comments = serializers.SerializerMethodField()
def get_number_of_likes(self, obj):
return obj.likes.count()
def get_number_of_comments(self, obj):
return obj.comments.count()
class Meta:
model = Post
fields = ['id', 'author', 'body', 'media', 'created_at', 'number_of_likes', 'number_of_comments']
read_only_fields = ['author', 'created_at', 'number_of_likes', 'number_of_comments']
class PostListCreateView(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def get_queryset(self):
user = self.request.user
return Post.objects.filter(author=user)
def perform_create(self, serializer):
serializer.save(author=self.request.user)
React Frontend Code
api.js
import axios from 'axios'
import { ACCESS_TOKEN } from './constants'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL
})
api.interceptors.request.use(
(config) => {
if (config.url && !config.url.includes('/api/user/register/')) {
const token = localStorage.getItem(ACCESS_TOKEN);
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
},
(error) => {
return Promise.reject(error)
}
)
export default api;
HomeView.jsx
const username = localStorage.getItem('username');
const { profile, loading } = useProfile(username);
const [postData, setPostData] = useState({
body: "",
media: null,
});
// Handle file change (e.g., media upload)
const handleFileChange = (e) => {
const { name, files } = e.target;
setPostData((prevData) => ({
...prevData,
[name]: files[0], // Update the file in postData
}));
};
// Handle text input changes
const handleChange = (e) => {
const { name, value } = e.target;
setPostData((prevData) => ({
...prevData,
[name]: value, // Update the body in postData
}));
};
// Handle form submission
const makePost = async (e) => {
e.preventDefault();
const formData = new FormData();
Object.keys(postData).forEach((key) => {
if (postData[key] !== "" && postData[key] !== null) {
formData.append(key, postData[key]);
}
});
try {
const response = await api.post("/api/posts/", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
if (response.status === 201) {
setPostData({ body: "", media: null });
alert("Successfully created post");
} else {
alert("Failed to create post");
}
} catch (err) {
console.error("Error creating post:", err.response?.data || err.message);
alert(`Error: ${err.response?.data?.detail || err.message}`);
}
};
Не указан класс аутентификации.
Добавьте переменную authentication_classes в PostListCreateView и поместите класс аутентификации в массив для использования.
Возможно, учитывая, что тип токена, передаваемого из axios, - Bearer, ожидается, что он будет использовать jwt-токены.
Если вы реализовали логику работы с токенами JWT с помощью библиотеки simple-jwt, вам необходимо добавить JWTAuthentication в массив authentication_classes.
from rest_framework_simplejwt.authentication import JWTAuthentication
class PostListCreateView(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
authentication_classes=[JWTAuthentication]
Для справки, 401 ошибка, похоже, возникает в процессе получения разрешения.
Если вы не указали классы аутентификации (authentication_classes), как в вашем случае, определите их как AnonymousUser и выполните проверку прав доступа.
Вы должны точно идентифицировать пользователя, отправившего запрос, через класс аутентификации, чтобы выполнить проверку разрешения.