利用 django-qr-code 库结合 Django 后端逻辑,可以实现 “扫描二维码登录” 功能。核心思路是:后端生成包含临时令牌的二维码,用户扫描后通过移动端确认登录,后端验证令牌有效性并完成登录状态绑定。以下是详细实现步骤:
一、原理概述
- 生成临时令牌:用户访问登录页时,后端生成一个唯一的临时令牌(
token),有效期较短(如 2 分钟),并关联到当前会话或设备。 - 生成二维码:将令牌编码为 URL(如
https://yourdomain.com/login/confirm?token=xxx),通过django-qr-code生成二维码,展示在登录页。 - 移动端扫描确认:用户用已登录的移动端 APP 扫描二维码,访问二维码中的 URL,后端验证令牌后,标记该令牌为 “已确认”。
- 前端轮询验证:登录页前端定期轮询后端,检查令牌是否已被确认。若确认,则为当前浏览器创建登录会话,完成登录。
二、环境准备
-
安装依赖:
pip install django-qr-code # 生成二维码 pip install django-cors-headers # 解决移动端跨域请求(若前后端分离) -
在
settings.py中配置:INSTALLED_APPS = [ # ... 'qr_code', # 注册二维码应用 'corsheaders', # 跨域支持(若需要) ] # 跨域配置(允许移动端域名访问) MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 放在首位 # ... 其他中间件 ] CORS_ALLOWED_ORIGINS = [ "https://your-mobile-app-domain.com", # 移动端域名 ]
三、实现步骤
1. 定义令牌模型(存储临时登录令牌)
创建 models.py,存储临时令牌及状态:
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
import uuid
class LoginToken(models.Model):
token = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # 唯一令牌
created_at = models.DateTimeField(auto_now_add=True) # 创建时间
confirmed = models.BooleanField(default=False) # 是否被移动端确认
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) # 关联的用户(确认后赋值)
def is_valid(self):
"""检查令牌是否有效(未过期且未确认)"""
# 有效期 2 分钟
return not self.confirmed and (timezone.now() - self.created_at).total_seconds() < 120
python manage.py makemigrations
python manage.py migrate
2. 生成二维码和令牌(登录页视图)
创建 views.py,处理登录页请求,生成令牌和二维码:
from django.shortcuts import render
from django.http import JsonResponse
from .models import LoginToken
import qrcode
from io import BytesIO
from django.core.files.base import ContentFile
from django.contrib.auth import login
from django.contrib.auth.models import User
def qr_login_page(request):
"""登录页:生成令牌和二维码"""
# 生成新的临时令牌
token = LoginToken.objects.create()
# 二维码包含的 URL(移动端扫描后访问该 URL 确认登录)
confirm_url = f"https://yourdomain.com/login/confirm/?token={token.token}"
# 传递令牌和二维码到模板(前端需轮询检查 token 状态)
return render(request, 'qr_login.html', {
'token': token.token,
'confirm_url': confirm_url, # 用于生成二维码
})
3. 模板中生成二维码(前端)
创建 templates/qr_login.html,使用 django-qr-code 生成二维码,并添加轮询逻辑:
<!DOCTYPE html>
<html>
<head>
<title>扫码登录</title>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
</head>
<body>
<h1>请用 APP 扫描二维码登录</h1>
<!-- 生成二维码:使用 qr_code 模板标签,内容为 confirm_url -->
{% load qr_code %}
{% qr_code confirm_url size="M" image_format="png" %}
<p>令牌:{{ token }}</p>
<p id="status">等待扫描...</p>
<script>
// 轮询检查令牌状态(每 2 秒一次)
const token = "{{ token }}";
const checkStatus = setInterval(() => {
$.getJSON(`/login/check/?token=${token}`, (data) => {
if (data.confirmed) {
clearInterval(checkStatus);
$("#status").text("登录成功,正在跳转...");
// 登录成功后跳转首页
setTimeout(() => window.location.href = "/", 1000);
} else if (!data.valid) {
clearInterval(checkStatus);
$("#status").text("二维码已过期,请刷新页面");
}
});
}, 2000);
</script>
</body>
</html>
4. 移动端确认登录接口
创建移动端扫描后访问的确认接口(views.py):
from django.http import JsonResponse
from .models import LoginToken
from django.contrib.auth.decorators import login_required # 确保移动端用户已登录
@login_required # 移动端用户必须先登录才能确认
def confirm_login(request):
"""移动端确认登录:将令牌与当前用户绑定"""
token_str = request.GET.get('token')
try:
token = LoginToken.objects.get(token=token_str)
if token.is_valid():
# 绑定当前移动端用户到令牌
token.user = request.user
token.confirmed = True
token.save()
return JsonResponse({"status": "success"})
else:
return JsonResponse({"status": "invalid token"}, status=400)
except LoginToken.DoesNotExist:
return JsonResponse({"status": "token not found"}, status=404)
5. 前端轮询检查接口
创建供前端轮询的令牌状态检查接口(views.py):
def check_token_status(request):
"""检查令牌是否已被确认,若确认则为当前会话登录"""
token_str = request.GET.get('token')
try:
token = LoginToken.objects.get(token=token_str)
if token.confirmed and token.user:
# 令牌已确认,为当前浏览器创建登录会话
login(request, token.user)
return JsonResponse({"confirmed": True, "valid": True})
return JsonResponse({
"confirmed": token.confirmed,
"valid": token.is_valid()
})
except LoginToken.DoesNotExist:
return JsonResponse({"confirmed": False, "valid": False})
6. 配置 URL 路由
在 urls.py 中添加路由:
from django.urls import path
from . import views
urlpatterns = [
path('login/qr/', views.qr_login_page, name='qr_login_page'), # 登录页(展示二维码)
path('login/confirm/', views.confirm_login, name='confirm_login'), # 移动端确认接口
path('login/check/', views.check_token_status, name='check_token'), # 轮询检查接口
]
四、核心逻辑说明
-
令牌安全性:
- 令牌使用
uuid生成,随机性强,难以猜测; - 有效期仅 2 分钟,降低被盗用风险;
- 确认时要求移动端用户已登录,确保操作人是账号所有者。
- 令牌使用
-
登录状态绑定:
- 移动端确认后,
LoginToken关联到用户并标记为 “已确认”; - 前端轮询时,若令牌已确认,后端调用
login(request, token.user)为当前浏览器创建 Django 登录会话,完成登录。
- 移动端确认后,
-
前端体验:
- 轮询间隔设为 2 秒,平衡实时性和服务器压力;
- 二维码过期后提示用户刷新页面重新生成。
五、扩展优化
- 二维码过期自动刷新:前端检测到令牌过期后,自动刷新页面重新生成二维码。
- 限制单令牌单设备:同一令牌只能被一个设备确认,避免重复登录。
- 添加扫码成功提示:移动端确认后,通过 WebSocket 实时通知前端(替代轮询,减少请求)。
- 日志记录:记录登录时间、设备信息等,用于安全审计。
通过以上步骤,即可实现基于 django-qr-code 的扫码登录功能,核心是利用临时令牌作为桥梁,连接前端登录页和移动端已登录状态,最终完成身份验证。