我正在按照这个视频教程尝试使用 DRF、djoser 和 React 实现 Google 社交身份验证。导致错误的步骤:发送 GET 请求:http://localhost:8000/auth/o/...
我正在关注这个 视频教程 和 DRF
, djoser
实现 Google 社交身份验证 React
.
导致错误的步骤:
-
发送
GET
请求:
http://localhost:8000/auth/o/google-oauth2/?redirect_uri=http://localhost:8000
响应如下所示(我稍微修改了响应,因为我不确定此 URL 是否可以安全共享)
{
"authorization_url": "https://accounts.google.com/o/oauth2/auth?client_id=836198290956-fe0ilujf6e23l882oumgkufi8qm6fg3m.apps.googleusercontent.com&redirect_uri=http://localhost:8000&state=eNwMFCmEplYgbUTTP9nnrQ6MduAPxzDY&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile+openid+openid+email+profile"
}
-
在我向浏览器输入响应后,它会将我重定向到 google
sign in
页面。然后我选择我的帐户,然后按继续。我现在被重定向到 localhost:8000
这个网址 L
http://localhost:8000/?state=eNwMFCmEplYgbUTTP9nnrQ6MduAPxzDY&code=4%2F0AcvDMrB6f3ZQuTD563Vxriu2n0VHmLEOHnDRqC6jD5BRm068jj2tyExxfZZJDFLAtcwYLg&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&hd=circle.help&prompt=consent
我的问题出现在这里
-
我从该 url 中获取状态和代码参数,然后使用 Postman 向以下 url 发出 POST 请求(没有任何主体,只有 url,但我还将 Content-Type 标头设置为 application/x-www-form-urlencoded ):
localhost:8000/auth/o/google-oauth2/?state=eNwMFCmEplYgbUTTP9nnrQ6MduAPxzDY&code=4%2F0AcvDMrB6f3ZQuTD563Vxriu2n0VHmLEOHnDRqC6jD5BRm068jj2tyExxfZZJDFLAtcwYLg
但作为回应,我收到了这个( 这是我的问题 ):
{
"non_field_errors": [
"Invalid state has been provided."
]
}
我尝试调试一下,发现原因就在这个模块上:
# venv/lib/python3.11/site-packages/social_core/backends/oauth.py
class OAuthAuth(BaseAuth):
...
# Other methods
def validate_state(self):
"""Validate state value. Raises exception on error, returns state
value if valid."""
if not self.STATE_PARAMETER and not self.REDIRECT_STATE:
return None
state = self.get_session_state()
request_state = self.get_request_state()
if not request_state:
raise AuthMissingParameter(self, "state")
elif not state:
raise AuthStateMissing(self, "state")
elif not constant_time_compare(request_state, state):
raise AuthStateForbidden(self)
else:
return state
...
# Other methods
在我的例子中,在 OAuthAuth.validate_state()
方法中, state
变量不同于 request_state
变量,而 request_state
(from OAuthAuth.validate_state()
) 与上述两个 URL 中的状态 (from urls) 相同,但 state
(from OAuthAuth.validate_state()
) 完全不同。我不知道它来自哪里,为什么
self.get_request_state()
返回与 URL 不同的状态?也许我做错了什么,我应该在 Postman 中传递一些 cookie?
UPD: 我尝试手动将正确的状态值从 url 分配给 state = self.get_session_state() 并且一切正常,我只是不知道为什么 self.get_session_state() 返回不正确的值?
以下是我已安装的软件包的列表:
asgiref==3.8.1
certifi==2024.7.4
cffi==1.16.0
charset-normalizer==3.3.2
cryptography==42.0.8
defusedxml==0.8.0rc2
Django==5.0.7
django-filter==24.2
django-templated-mail==1.1.1
djangorestframework==3.15.2
djangorestframework-simplejwt==5.3.1
djoser==2.2.3
idna==3.7
Markdown==3.6
oauthlib==3.2.2
psycopg==3.2.1
psycopg2-binary==2.9.9
pycparser==2.22
PyJWT==2.8.0
python3-openid==3.2.0
requests==2.32.3
requests-oauthlib==2.0.0
social-auth-app-django==5.4.2
social-auth-core==4.5.4
sqlparse==0.5.1
typing_extensions==4.12.2
urllib3==2.2.2
这是我的设置.py
SECRET_KEY = "django_secret_key"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# Third party apps
"rest_framework",
"djoser",
"social_django",
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist", # more smooth with migration
# My apps
"accounts"
]
MIDDLEWARE = [
"social_django.middleware.SocialAuthExceptionMiddleware",
"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 = "djSocAuth.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
# "DIRS": [BASE_DIR / 'templates'], # os.path.join(BASE_DIR, "build")
"DIRS": [os.path.join(BASE_DIR, "build")], # os.path.join(BASE_DIR, "build")
"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",
"social_django.context_processors.backends",
"social_django.context_processors.login_redirect"
],
},
},
]
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": {
"rest_framework.permissions.IsAuthenticated"
},
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
AUTHENTICATION_BACKENDS = (
"social_core.backends.google.GoogleOAuth2", # Enable using google OAuth 2
"django.contrib.auth.backends.ModelBackend", # Enable logining via email and password
)
SIMPLE_JWT = {
'AUTH_HEADER_TYPES': ('JWT',),
"ACCESS_TOKEN_LIFETIME": timedelta(days=10000),
"REFRESH_TOKEN_LIFETIME": timedelta(days=10000),
"AUTH_TOKEN_CLASSES": (
"rest_framework_simplejwt.tokens.AccessToken",
)
}
from djoser.social.token.jwt import TokenStrategy
DJOSER = {
"LOGIN_FIELD": "email",
"USER_CREATE_PASSWORD_RETYPE": True, # confirm password field
"USERNAME_CHANGED_EMAIL_CONFIRMATION": True, # whenever username is changed - confirmation email is sent
"PASSWORD_CHANGED_EMAIL_CONFIRMATION": True,
"SET_USERNAME_RETYPE": True,
"SET_PASSWORD_RETYPE": True,
"PASSWORD_RESET_CONFIRM_URL": "password/reset/confirm/{uid}/{token}",
"USERNAME_RESET_CONFIRM_URL": "email/reset/confirm/{uid}/{token}",
"ACTIVATION_URL": "activate/{uid}/{token}", # this should be on frontend, --> auth/users/activation/
"SEND_ACTIVATION_EMAIL": True,
"SOCIAL_AUTH_TOKEN_STRATEGY": "djoser.social.token.jwt.TokenStrategy",
"SOCIAL_AUTH_ALLOWED_REDIRECT_URIS": [
"http://localhost:8000"
],
"SERIALIZERS": {
"user_create": "accounts.serializers.UserCreateSerializer",
"user": "accounts.serializers.UserCreateSerializer",
"current_user": "accounts.serializers.UserCreateSerializer",
"user_delete": "djoser.serializers.UserDeleteSerializer",
}
}
# Google config
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = "my_key"
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = "my_secret"
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
"https://www.googleapis.com/auth/userinfo.email", # retrieve email
"https://www.googleapis.com/auth/userinfo.profile",
"openid"
] # when people go to sign up and sing in, they are going to retrieve some data from their accounts
SOCIAL_AUTH_GOOGLE_OAUTH2_EXTRA_DATA = ["first_name", "last_name"]
AUTH_USER_MODEL = "accounts.UserAccount"
...
我发现了很多类似的问题,但是并没有完全解释这个错误的原因是什么。