You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Rest会话存储OTP前端验证失败,Postman测试正常求助

问题:Session存储OTP跨请求丢失(Postman正常,前端调用失败)

项目环境

  • 后端:Django Rest Framework
  • 前端:WordPress(自定义HTML/JS实现OTP验证流程)
  • 核心逻辑:OTP不存储到数据库,临时存在Session中,用户提交OTP时从Session读取验证

问题现象

  • Postman测试/send_otp/和/login/接口,Session中的OTP可正常读取,登录验证功能正常
  • 前端HTML/JS调用时,/send_otp/接口成功生成并存储OTP,但/login/接口读取request.session.get("otp_code")返回None,导致OTP验证失败

相关代码

后端视图代码

SendOTPView

class SendOTPView(APIView):
    def post(self, request):
        serializer = OTPSerializer(data=request.data)
        if serializer.is_valid():
            phone_number = serializer.validated_data["phone"]
            otp_code = randint(100000, 999999)
            request.session['otp_code'] = otp_code
            print("otp in sendOTP",request.session.get("otp_code"))
            otp_send(phone_number, otp_code)
            return Response(
                {"detail": "OTP sent successfully"}, status=status.HTTP_200_OK
            )
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

UserLoginView

class UserLoginView(APIView):
    def post(self, request):
        serializer = UserLoginSerializer(data=request.data)
        if serializer.is_valid():
            stored_otp = request.session.get("otp_code")
            print(stored_otp)
            user_entered_otp = serializer.validated_data["otp"]
            phone_number = serializer.validated_data["phone"]
            try:
                user_from_db = User.objects.get(username=phone_number)
            except:
                return Response({"detail": "user not found"}, status=status.HTTP_404_NOT_FOUND)
            password = generate_password(phone_number)
            if str(user_entered_otp) == str(stored_otp):
                del request.session['otp_code']
                user = authenticate(username=phone_number, password=password)

                if user:
                    return Response({"detail": 'logged in successfully '}, status=status.HTTP_200_OK)
                else:
                    return Response(
                        {"detail": "Invalid phone or otp"},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
            else :
                return Response(
                    {
                        "detail": "Wrong otp code",
                    },
                    status=status.HTTP_400_BAD_REQUEST,)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

前端HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="{{ csrf_token }}">
    <title>OTP Verification</title>
</head>
<body>
    <div id="phone-form">
        <h2>Send OTP</h2>
        <input type="text" id="phone" placeholder="Enter your phone number">
        <button onclick="sendOTP()">Send OTP</button>
    </div>

    <div id="otp-form" style="display: none;">
        <h2>Verify OTP</h2>
        <input type="hidden" id="otp-phone">
        <input type="text" id="otp" placeholder="Enter OTP">
        <button onclick="verifyOTP()">Verify OTP</button>
    </div>

    <script>
        const baseURL = 'http://127.0.0.1:8000/auth';

        function getCSRFToken() {
            return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
        }

        function sendOTP() {
            const phone = document.getElementById('phone').value;

            fetch(`${baseURL}/send_otp/`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRFToken': getCSRFToken()
                },
                credentials: 'include',
                body: JSON.stringify({ phone })
            })
            .then(response => {
                if (response.ok) {
                    alert('OTP sent successfully');
                    document.getElementById('phone-form').style.display = 'none';
                    document.getElementById('otp-form').style.display = 'block';
                    document.getElementById('otp-phone').value = phone;
                } else {
                    alert('Failed to send OTP');
                }
            })
            .catch(error => console.error('Error:', error));
        }

        function verifyOTP() {
            const phone = document.getElementById('otp-phone').value;
            const otp = document.getElementById('otp').value;

            fetch(`${baseURL}/login/`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRFToken': getCSRFToken()
                },
                credentials: 'include',
                body: JSON.stringify({ phone, otp })
            })
            .then(response => {
                if (response.ok) {
                    alert('Login successful');
                    window.location.href = '/';
                } else {
                    alert('Failed to verify OTP');
                }
            })
            .catch(error => console.error('Error:', error));
        }
    </script>
</body>
</html>

前端JS代码

const baseURL = 'http://127.0.0.1:8000/auth';

function getCSRFToken() {
    const cookieValue = document.cookie
        .split('; ')
        .find(row => row.startsWith('csrftoken='))
        .split('=')[1];
    return cookieValue;
}

function sendOTP() {
    const phone = document.getElementById('phone').value;

    fetch(`http://127.0.0.1:8000/auth/send_otp/`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': getCSRFToken()
        },
        credentials: 'include',
        body: JSON.stringify({ phone })
    })
    .then(response => {
        if (response.ok) {
            alert('OTP sent successfully');
            document.getElementById('phone-form').style.display = 'none';
            document.getElementById('otp-form').style.display = 'block';
            document.getElementById('otp-phone').value = phone;
        } else {
            alert('Failed to send OTP');
        }
    })
    .catch(error => console.error('Error:', error));
}

function verifyOTP() {
    const phone = document.getElementById('otp-phone').value;
    const otp = document.getElementById('otp').value;

    fetch(`http://127.0.0.1:8000/auth/login/`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': getCSRFToken()
        },
        credentials: 'include',
        body: JSON.stringify({ phone, otp })
    })
    .then(response => {
        if (response.ok) {
            alert('Login successful');
            window.location.href = '/';
        } else {
            alert('Failed to verify OTP');
        }
    })
    .catch(error => console.error('Error:', error));
}

排查方向与解决方法

1. 跨域Cookie/Session共享问题

这是最可能的原因——前端WordPress和后端Django端口/域名不同,属于跨域场景,浏览器默认会限制Cookie的跨域携带:

  • 修改Django配置文件settings.py:
    # 允许跨域携带凭证
    CORS_ALLOW_CREDENTIALS = True
    # 添加前端域名到允许列表(比如WordPress运行在http://localhost:80)
    CORS_ALLOWED_ORIGINS = [
        "http://localhost:80",
        "http://127.0.0.1:80",
    ]
    # 设置Session Cookie跨域可用
    SESSION_COOKIE_SAMESITE = 'None'
    # 生产环境必须设为True(HTTPS),本地测试可暂时设为False
    SESSION_COOKIE_SECURE = False
    # 同步设置CSRF Cookie
    CSRF_COOKIE_SAMESITE = 'None'
    CSRF_COOKIE_SECURE = False
    
  • 确保前端fetch请求的credentials参数为'include'(你的代码已设置,无需修改)

2. CSRF Token一致性问题

  • 前端HTML中的{{ csrf_token }}是WordPress的模板变量,无法生成Django的CSRF Token,必须使用从Cookie中读取Token的JS版本(即你提供的单独JS代码中的getCSRFToken方法)
  • 替换HTML中的script部分为正确的JS代码,避免使用meta标签中的错误CSRF Token

3. Session存储验证

  • 检查Django的Session配置,确保SESSION_ENGINE使用的是可用的存储后端(默认django.contrib.sessions.backends.db需执行python manage.py migrate创建Session表)
  • 在两个视图中打印request.session.session_key,确认两次请求的session_key是否一致:如果不一致,说明请求没有共享同一个Session,需检查Cookie是否正确携带

4. 浏览器调试

  • 打开浏览器开发者工具的Network面板,查看/send_otp/和/login/请求的Request Headers,确认Cookie字段中包含sessionid和csrftoken
  • 查看Response Headers中的Set-Cookie字段,确认Session Cookie的属性(SameSite、Secure)符合配置

内容的提问来源于stack exchange,提问作者Novartis Nk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:52:07