使用Curl通过Azure AD授权访问Django API时跳转登录页问题排查
问题:携带Azure AD Access Token访问Django API仍跳转至登录页面
问题现象
我负责的企业Django项目采用Azure Active Directory认证,通过Django REST Framework搭建API。使用Curl生成Access Token后,携带Token请求API时,会被重定向到Microsoft登录页面,仿佛Token未生效。
操作步骤
1. 生成Access Token的Curl命令
curl -X POST https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token -H "Content-Type: application/x-www-form-urlencoded" -d "client_id={client_id}" -d "client_secret={client_secret}" -d "grant_type=client_credentials" -d "redirect_uri=https://{my_domain}/oauth2/callback" -d "scope=api://{client_id}/.default openid profile email"
2. 携带Token请求API的命令
curl -X GET -L https://<my_domain>/api/benches/1/status/ -H "Authorization: Bearer <token>"
返回结果
返回内容为Microsoft登录页面的HTML,开头部分如下:
<!-- Copyright (C) Microsoft Corporation. All rights reserved. --> <!DOCTYPE html> <html dir="ltr" class="" lang="en"> <head> <title>Sign in to your account</title> ...
Curl Verbose模式输出
Note: Unnecessary use of -X or --request, GET is already inferred. * Host <my_domain>:443 was resolved. * IPv6: (none) * IPv4: ip * Trying ip:443... * Connected to <my_domain> (ip) port 443 * schannel: disabled automatic use of client certificate * ALPN: curl offers http/1.1 * ALPN: server accepted http/1.1 * using HTTP/1.x > GET /api/benches/1/status/ HTTP/1.1 > Host: <my_domain> > User-Agent: curl/8.7.1 > Accept: */* > Authorization: Bearer <token> > * schannel: remote party requests renegotiation * schannel: renegotiating SSL/TLS connection * schannel: SSL/TLS connection renegotiated * schannel: remote party requests renegotiation * schannel: renegotiating SSL/TLS connection * schannel: SSL/TLS connection renegotiated < HTTP/1.1 302 Found < Server: nginx/1.14.1 < Date: Wed, 17 Jul 2024 16:08:34 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 0 < Connection: keep-alive < Location: /oauth2/login?next=/api/benches/1/status/ < X-Frame-Options: DENY < Vary: Cookie < X-Content-Type-Options: nosniff < Referrer-Policy: same-origin < * Request completely sent off * Connection #0 to host <my_domain> left intact
疑问
是否是Azure应用中缺少必要权限?
Django项目配置(settings.py)
""" Django settings for core project. Generated by 'django-admin startproject' using Django 5.0.4. For more information on this file, see https://docs.djangoproject.com/en/5.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.0/ref/settings/ """ from pathlib import Path import os from dotenv import load_dotenv load_dotenv() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.getenv('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ['my_domain'] AUTHENTICATION_BACKENDS = ( 'django_auth_adfs.backend.AdfsAuthCodeBackend', 'django_auth_adfs.backend.AdfsAccessTokenBackend', ) # Application definition INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # Installed apps 'django_auth_adfs', 'bench', 'api', 'rest_framework', ] 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", # Other Middlewares 'django_auth_adfs.middleware.LoginRequiredMiddleware', ] ROOT_URLCONF = "core.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", 'DIRS': [ BASE_DIR / 'static/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 = "core.wsgi.application" # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } } # Password validation # https://docs.djangoproject.com/en/5.0/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/5.0/topics/i18n/ LANGUAGE_CODE = "en-us" TIME_ZONE = "GB" USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ STATIC_URL = '/static/' MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' STATIC_ROOT = '/root/to/static' STATICFILES_DIRS = [ BASE_DIR / 'static', ] # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" LOGIN_URL = 'django_auth_adfs:login' LOGOUT_URL = 'django_auth_adfs:logout' LOGIN_REDIRECT_URL = 'https://my_domain/oauth2/callback' # Client secret is not public information. Should store it as an environment variable. client_id = os.getenv('client_id') client_secret = os.getenv('client_secret') tenant_id = os.getenv('tenant_id') AUTH_ADFS = { 'AUDIENCE': client_id, 'CLIENT_ID': client_id, 'CLIENT_SECRET': client_secret, 'CLAIM_MAPPING': {'first_name': 'given_name', 'last_name': 'family_name', 'email': 'upn' }, 'GROUPS_CLAIM': 'roles', 'MIRROR_GROUPS': True, 'USERNAME_CLAIM': 'email', 'TENANT_ID': tenant_id, 'RELYING_PARTY_ID': client_id, 'LOGIN_EXEMPT_URLS': [ '^api', # Assuming you API is available at /api ], } SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_SSL_REDIRECT = True REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'django_auth_adfs.rest_framework.AdfsAccessTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), }
Azure应用权限配置
已为应用分配Benches.Change.All权限,权限面板截图如下:
内容的提问来源于stack exchange,提问作者Arad Soutehkeshan
相关产品推荐
相关产品推荐

