提问人:Asudox 提问时间:11/17/2023 最后编辑:Asudox 更新时间:11/19/2023 访问量:43
Django “request.user” 总是返回 “AnonymousUser”,即使 sessionid 被添加并且是有效的
Django "request.user" always returns "AnonymousUser" even though sessionid gets added and is valid
问:
所以我在没有使用 AbstractUser 或 AbstractBaseModel 等用户模型的情况下进行了不和谐的 oauth2 登录,当我一个月前第一次登录时,它运行良好,登录完美无缺!(已加载个人资料、显示用户名等)我不知道一周前我改变了什么,但它不再起作用了。自定义身份验证有效,authenticate() 返回一个新的 DiscordUser 模型,然后通过自定义后端参数将其传递给 login()。当我检查它时,sessionid 会保存在 cookie 存储中。当我也检查数据库时,sessionid 在那里,因此是一个有效的 sessionid。然而,当我打印出request.user或request.user.is_authenticated时,我总是得到AnonymousUser和False。我不明白。我关注了这个视频:https://www.youtube.com/watch?v=KKJyU7JGuVk&list=PLwu1p7jK5YEUhRNJpzLYa8nhWRnJdPR6p
auth.py:
from django.contrib.auth.backends import BaseBackend
from .models import DiscordUser
class DiscordAuthenticationBackend(BaseBackend):
def authenticate(self, request, user) -> DiscordUser:
try:
user = DiscordUser.objects.get(pk=user["id"])
return user
except DiscordUser.DoesNotExist:
new_user = DiscordUser.objects.create_discord_user(user)
new_user.save()
return new_user
managers.py
from django.contrib.auth import models
from datetime import datetime
from utils.cipher import XChaCha20Manager
from base64 import b64encode
class DiscordUserOAuth2Manager(models.UserManager):
def create_discord_user(self, user):
# encrypt tokens with the same nonce
encrypted_access_token, nonce = XChaCha20Manager().encrypt(user["access_token"].encode())
encrypted_refresh_token, _ = XChaCha20Manager(nonce=nonce).encrypt(user["refresh_token"].encode())
new_user = self.create(
user_id=user["id"],
username = f"{user["username"]}",
avatar = user["avatar"],
encrypted_access_token = b64encode(encrypted_access_token).decode(),
encrypted_refresh_token = b64encode(encrypted_refresh_token).decode(),
encryption_nonce = b64encode(nonce).decode(),
last_login = datetime.now(),
)
return new_user
models.py
from .managers import DiscordUserOAuth2Manager
from utils.cipher import XChaCha20Manager
from base64 import b64decode
from django.db import models
class VerificationSession(models.Model):
user_id = models.BigIntegerField(primary_key=True)
status = models.BooleanField(default=False)
class DiscordUser(models.Model):
objects = DiscordUserOAuth2Manager()
user_id = models.BigIntegerField(primary_key=True)
username = models.CharField(max_length=100, unique=True)
avatar = models.CharField(max_length=36)
encrypted_access_token = models.CharField(max_length=100) # stored in base64
encrypted_refresh_token = models.CharField(max_length=100) # stored in base64
encryption_nonce = models.CharField(max_length=100) # stored in base64
is_premium = models.BooleanField(default=False)
is_authenticated = True
last_login = models.DateTimeField(null=True)
async def get_token(self, user_id: str, type: str) -> str | None:
try:
user = await DiscordUser.objects.aget(pk=user_id)
match type:
case "access":
return XChaCha20Manager(nonce=b64decode(user.encryption_nonce)).decrypt(b64decode(user.encrypted_access_token))
case "refresh":
return XChaCha20Manager(nonce=b64decode(user.encryption_nonce)).decrypt(b64decode(user.encrypted_refresh_token))
except DiscordUser.DoesNotExist:
return None
views.py
from django.http import HttpResponse, HttpRequest, HttpResponseRedirect
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from securifier.settings import BASE_DIR
from typing import Union
import httpx, dotenv
# Create your views here.
ENV_PATH = BASE_DIR / ".env"
CLIENT_ID = dotenv.get_key(ENV_PATH, "CLIENT_ID")
OAUTH2_REDIRECT_URI = dotenv.get_key(ENV_PATH, "OAUTH2_REDIRECT_URI")
CLIENT_SECRET = dotenv.get_key(ENV_PATH, "CLIENT_SECRET")
DISCORD_AUTH_URL = fr"https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&redirect_uri={OAUTH2_REDIRECT_URI}&response_type=code&scope=identify%20guilds"
DISCORD_BOT_INVITE = fr"https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&permissions=8&scope=bot"
def index(request: HttpRequest) -> HttpResponse:
print(request.user)
print(request.user.is_authenticated)
return render(request, "index/index.html")
def botInvite(request: HttpRequest) -> HttpResponse:
return redirect(DISCORD_BOT_INVITE)
# Discord OAuth2 Login
def discordLogin(request: HttpRequest) -> HttpResponseRedirect:
if request.user.is_authenticated:
return redirect("index")
return redirect(DISCORD_AUTH_URL)
@login_required(login_url="/oauth2")
def logoutUser(request: HttpRequest) -> HttpResponseRedirect:
logout(request)
return redirect("index")
def exchangeOauth2Code(code) -> Union[str, None]:
BASE_API_URI = r"https://discord.com/api/v10"
data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': OAUTH2_REDIRECT_URI
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
try:
response = httpx.post(f"{BASE_API_URI}/oauth2/token", data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
response.raise_for_status()
access_token, refresh_token = response.json()["access_token"], response.json()["refresh_token"]
user_data = httpx.get(f"{BASE_API_URI}/users/@me", headers={"Authorization": f"Bearer {access_token}"})
user_data = user_data.json()
user_data.update({"access_token": access_token, "refresh_token": refresh_token}) # add tokens to user_data
return user_data
except Exception as e:
print(e)
return None
def discordLoginCallback(request) -> HttpResponseRedirect:
code = request.GET.get("code")
user = exchangeOauth2Code(code)
if user: # login
discord_user = authenticate(request, user=user)
login(request, user=discord_user, backend="index.auth.DiscordAuthenticationBackend")
print("returning to index")
return redirect("index")
else:
context = {
"msg": "An error occurred while the OAuth2 authorization process"
}
return render(request, "index/message.html", context)
settings.py
"""
Django settings for securifier project.
Generated by 'django-admin startproject' using Django 4.2.6.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
from pathlib import Path
from dotenv import get_key as env_get
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
ENV_PATH = BASE_DIR / ".env"
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '...'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
AUTHENTICATION_BACKENDS = [
"index.auth.DiscordAuthenticationBackend",
]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"adrf", # async django rest framework
"index",
]
MIDDLEWARE = [
'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 = 'securifier.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / "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 = 'securifier.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": env_get(ENV_PATH, "POSTGRE_DB_NAME"),
"USER": env_get(ENV_PATH, "POSTGRE_DB_USERNAME"),
"PASSWORD": env_get(ENV_PATH, "POSTGRE_DB_PASSWORD"),
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/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/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Amsterdam'
USE_I18N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / "static"
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
我一直在花几个小时试图解决这个问题,但我没有让它工作。 非常感谢帮助!
答:
2赞
Asudox
11/19/2023
#1
似乎缺少自定义身份验证后端中的 get_user() 函数。将函数添加到自定义身份验证后端解决了以下问题:
from django.contrib.auth.backends import BaseBackend
from .models import DiscordUser
class DiscordAuthenticationBackend(BaseBackend):
def authenticate(self, request, user) -> DiscordUser:
if returned_user := self.get_user(user["id"]):
return returned_user
else:
new_user = DiscordUser.objects.create_discord_user(user)
new_user.save()
return new_user
def get_user(self, user_id):
try:
return DiscordUser.objects.get(pk=user_id)
except DiscordUser.DoesNotExist:
return None
评论