如何实现外部应用用户免密登录Django应用并自动创建用户?
方案选型建议
你的需求是信任双边应用间的无凭证自动登录+用户自动创建,不需要第三方身份提供商,所以基于共享密钥的HMAC签名验证方案是最适合的——比SSO/OAuth轻量得多,不需要复杂的授权流程,同时能满足安全性要求。
为什么不选SSO/OAuth?
- SSO需要独立的身份提供商(IDP),你们没有第三方账号需求,搭建IDP反而增加复杂度
- OAuth是授权框架,适合跨平台的第三方授权场景,这里是双边信任集成,用OAuth属于过度设计
你最初设想的“加密Token”思路方向正确,但更推荐用签名验证而非加密:加密是为了保密数据,而你们的核心需求是验证请求来自可信的第三方、数据未被篡改,签名(比如HMAC)更直接且性能更好。
双方应用职责划分
第三方发起应用
- 触发跳转:提供用户点击的按钮,触发向你的Django应用跳转的逻辑
- 生成请求参数:整理用户邮箱、必要的用户附加数据(如部门、姓名),添加时间戳(用于防止重放攻击)
- 生成签名:用双方约定的共享密钥,对参数集合进行HMAC签名
- 构造跳转URL:将用户数据、时间戳、签名作为GET参数拼接在你的授权回调URL后
你的Django应用
- 提供授权回调视图:专门处理第三方跳转过来的请求
- 签名验证:按照相同规则重新计算签名,与请求中的签名比对,同时验证时间戳有效性
- 用户管理:根据邮箱查询用户,不存在则自动创建(使用Django原生
User模型或自定义用户模型) - 自动登录:验证通过后,为用户创建Django会话,完成自动登录
- 页面跳转:登录完成后跳转到指定页面(如应用首页)
具体实现指导
1. 准备共享密钥
双方约定一个强密钥,分别存在各自应用的环境变量中,不要硬编码。比如在你的Django项目中,在settings.py里:
import os SHARED_SECRET_KEY = os.environ.get('SHARED_INTEGRATION_SECRET')
2. 第三方应用的签名生成逻辑(示例,以Python为例)
import hmac import hashlib import time import urllib.parse import json def generate_auth_url(django_callback_url, user_email, user_data, secret_key): # 1. 准备参数,必须包含时间戳 timestamp = str(int(time.time())) params = { 'email': user_email, 'user_data': json.dumps(user_data), # 附加数据转JSON字符串 'timestamp': timestamp } # 2. 按照固定顺序拼接参数(双方必须一致,否则签名会不一致) sorted_params = sorted(params.items()) param_string = '&'.join([f"{k}={urllib.parse.quote(v)}" for k, v in sorted_params]) # 3. 生成HMAC-SHA256签名 signature = hmac.new( secret_key.encode('utf-8'), param_string.encode('utf-8'), hashlib.sha256 ).hexdigest() # 4. 构造最终跳转URL params['signature'] = signature return f"{django_callback_url}?{urllib.parse.urlencode(params)}"
3. Django端的授权回调视图实现
from django.shortcuts import redirect from django.contrib.auth import login, get_user_model from django.http import HttpResponseForbidden import hmac import hashlib import urllib.parse import time import json from django.conf import settings User = get_user_model() def third_party_auth(request): # 1. 获取请求参数 email = request.GET.get('email') user_data_str = request.GET.get('user_data') timestamp = request.GET.get('timestamp') signature = request.GET.get('signature') # 2. 参数合法性检查 if not all([email, timestamp, signature]): return HttpResponseForbidden("Invalid request parameters") # 3. 验证时间戳(防止重放攻击,比如限制5分钟内有效) try: timestamp_int = int(timestamp) if time.time() - timestamp_int > 300: # 300秒=5分钟 return HttpResponseForbidden("Request expired") except ValueError: return HttpResponseForbidden("Invalid timestamp") # 4. 重新计算签名,与请求中的签名比对 params_to_sign = { 'email': email, 'user_data': user_data_str, 'timestamp': timestamp } sorted_params = sorted(params_to_sign.items()) param_string = '&'.join([f"{k}={urllib.parse.quote(v)}" for k, v in sorted_params]) computed_signature = hmac.new( settings.SHARED_SECRET_KEY.encode('utf-8'), param_string.encode('utf-8'), hashlib.sha256 ).hexdigest() if computed_signature != signature: return HttpResponseForbidden("Invalid signature") # 5. 处理用户:存在则获取,不存在则创建 user_data = json.loads(user_data_str) if user_data_str else {} user, created = User.objects.get_or_create( email=email, defaults={ 'username': email.split('@')[0], # 用邮箱前缀作为默认用户名 'first_name': user_data.get('first_name', ''), 'last_name': user_data.get('last_name', '') } ) # 6. 自动登录用户 login(request, user) # 7. 跳转到目标页面(比如首页) return redirect('/')
4. 配置Django URL
在项目的urls.py中添加回调URL:
from django.urls import path from .views import third_party_auth urlpatterns = [ # ... 其他URL path('auth/third-party/', third_party_auth, name='third_party_auth'), ]
安全注意事项
- 必须使用HTTPS传输所有请求,防止参数被窃听或篡改
- 时间戳验证不可省略,避免攻击者重复使用旧的合法请求
- 共享密钥要定期轮换,且不要在任何日志或代码中泄露
- 限制用户数据的范围,只传输必要信息,不要包含敏感数据
- 如果你的Django使用自定义用户模型,调整
get_or_create中的字段即可
内容的提问来源于stack exchange,提问作者Aikanáro
相关产品推荐
相关产品推荐

