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

Django Channels WebSocket实时聊天应用遇CSRF验证失败及连接问题求助

Django Channels WebSocket聊天应用CSRF验证失败及连接问题

我是Django新手,正在搭建基于Channels WebSocket的实时聊天应用。不管是自己编写聊天页面还是直接复制教程代码,终端一直提示“CSRF验证失败,请求中止”和“禁止访问(缺少CSRF令牌)”。

已尝试的方案:

  • 在表单上方添加{% csrf_token %}——页面会刷新,消息未加入消息列表,但“禁止访问(缺少CSRF令牌)”提示消失;
  • 在chat/views.py中添加@csrf_protect装饰器;
  • 使用教程指定的Channels 3.0.5版本;
  • 重建数据库、删除并恢复所有迁移文件,无任何变化。

相关代码

chat/templates/chat/chat.html

{% extends 'core/base.html' %}

{% block title %} {{ chat.name }} | {% endblock %}

{% block content %}


<div class="p-10 lg:p-20 text-center">
    <h1 class="text-3xl lg:text-6xl text-white">{{ chat.name }}</h1>

</div>

<!-- div for actual messages-->
<div class="lg:w-2/4 mt-6 mx-4 lg:mx-auto p-4 bg-white rounded-xl">
    <div class="chat-messages space-y-3" id="chat-messages">
        <div class="p-4 bg-gray-200 rounded-xl">
            <p class="font-semibold">Username</p>
            <p>This is the message for the user</p>
        </div>

        <div class="p-4 bg-gray-200 rounded-xl">
            <p class="font-semibold">Username</p>
            <p>This is the message for the user</p>
        </div>

        <div class="p-4 bg-gray-200 rounded-xl">
            <p class="font-semibold">Username</p>
            <p>This is the message for the user</p>
        </div>

        <div class="p-4 bg-gray-200 rounded-xl">
            <p class="font-semibold">Username</p>
            <p>This is the message for the user</p>
        </div>
    </div>
</div>
<!-- form for input and sending-->
<div class="lg:w-2/4 mt-6 mx-4 lg:mx-auto p-4 bg-white rounded-xl">
    <form method="post" action="." class="flex">
        {% csrf_token %}
        <input type="text" name="content" class="flex-1 mr-3" placeholder="Your message..." id="chat-massage-input">
        <button class="px-5 py-3 rounded-xl text-white bg-teal-600 hover:bg-teal-700" id="chat-message-submit">Submit</button>
    </form>
</div>

{% endblock %}

{% block scripts %}
{{ chat.slug | json_script:"json-chatname"}}
{{ request.user.username | json_script:"json-username" }}

<script>
    const chatName = JSON.parse(document.getElementById("json-chatname").textContent);
    const userName = JSON.parse(document.getElementById("json-username").textContent);
    const chatSocket = new WebSocket(
        "ws://"
        + window.location.host
        + "/ws/"
        + chatName
        + "/"
    );

    chatSocket.onopen = function(e) {
        console.log("[open] Connection established");
    };

    chatSocket.onmessage = function(e){
        console.log("on message");

        const data = JSON.parse(e.data); 

        if (data.message){
            let html = '<div class="p-4 bg-gray-200 rounded-xl">'
            html += '<p class="font-semibold">' + data.username + '</p>'
            html += '<p>' + data.message + '</p> </div>' 

            document.querySelector("#chat-messages").innerHTML += html;
        }else{
            alert("The message is empty");
        }
    };

    chatSocket.onclose = function(e){
        console.log("on close");
    }

    document.querySelector("#chat-message-submit").onclick() = function(e){

        e.preventDefault();
        const messageInputDom = document.querySelector("#chat-message-input");
        const message = messageInputDom.value;

        chatSocket.send(JSON.stringify({
            "message" : message,
            "username" : userName,
            "chatname" : chatName,
        }))

        messageInputDom.value = "";

        return false;
    }


</script>
{% endblock %}

chat/consumers.py

import json

from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.chat_name = self.scope["url_route"]["kwargs"]["chat_name"]
        self.chat_group_name = "%s_group" % self.chat_name 

        # add group to channel layers
        await self.channel_layer.group_add(
            self.chat_group_name,
            self.channel_name
        )

        # connect to channel
        await self.accept()

    async def disconnect(self):
        await self.channel_layer.group_discard(
            self.chat_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        data = json.loads(text_data)
        message = data["message"]
        username = data["username"]
        chat = data["chat"]

        await self.channel_layer.group_send(
            self.chat_group_name,
            {
                "type": "chat_message",
                "message":message,
                "username":username,
                "chat":chat,
            }
        )
    
    async def chat_message(self, event):
        message = event["message"]
        username = event["username"]
        chat = event["chat"]

        await self.send(text_data=json.dumps({
            "message": message,
            "username": username,
            "chat": chat,
        }))

chat/models.py

from django.db import models

# Create your models here.
class Chat(models.Model):
    name = models.CharField(max_length=255)
    slug = models.SlugField(unique=True)

chat/views.py

from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_protect
# Create your views here.
from .models import Chat

@login_required
@csrf_protect
def chats(request):
    chats = Chat.objects.all

    return render(request, "chat/chats.html", {"chats" : chats})
        

@login_required
@csrf_protect
def chat(request, slug):
    chat = Chat.objects.get(slug=slug)

    return render(request, "chat/chat.html", {"chat" : chat})

djangochat/asgi.py

import os

from django.core.asgi import get_asgi_application

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter

import chat.routing 
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangochat.settings')

django_asgi_app = get_asgi_application()



application = ProtocolTypeRouter({
    "http" : django_asgi_app,
    "websocket" : AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    )
})

补充说明

  • 完全看不到WebSocket握手的迹象;
  • 点击提交时,即使消息为空,console.log无输出,alert也无提示;
  • 尝试调试WebSocket连接,但因连接未建立,不知如何推进。

问题解决思路

1. 修复JavaScript事件绑定语法错误

代码中点击事件绑定存在语法错误,多了一对括号,导致事件无法正常绑定,表单会执行默认提交行为:

// 错误写法
document.querySelector("#chat-message-submit").onclick() = function(e){...}

// 正确写法
document.querySelector("#chat-message-submit").onclick = function(e){...}

2. 添加WebSocket连接的CSRF令牌验证

Channels的WebSocket连接需要携带CSRF令牌,需在创建连接时传入:

{% block scripts %}
{{ chat.slug | json_script:"json-chatname"}}
{{ request.user.username | json_script:"json-username" }}
{% csrf_token %}
<script>
    const chatName = JSON.parse(document.getElementById("json-chatname").textContent);
    const userName = JSON.parse(document.getElementById("json-username").textContent);
    // 获取页面中的CSRF令牌
    const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
    const chatSocket = new WebSocket(
        "ws://"
        + window.location.host
        + "/ws/"
        + chatName
        + "/?csrfmiddlewaretoken=" + csrfToken
    );
    // 后续代码不变
</script>
{% endblock %}

3. 修复前后端数据字段不匹配问题

前端发送的字段是chatname,但Consumer中尝试获取chat字段,会触发KeyError,需统一字段名:

// 前端修改发送字段为chat
chatSocket.send(JSON.stringify({
    "message" : message,
    "username" : userName,
    "chat" : chatName,
}))

4. 修正Consumer中的Group移除方法

Channels中没有group_discard方法,正确移除连接的方法是group_remove:

async def disconnect(self):
    await self.channel_layer.group_remove(
        self.chat_group_name,
        self.channel_name
    )

5. 确认WebSocket路由配置

确保chat/routing.py的路由配置与前端连接路径匹配:

# chat/routing.py
from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/(?P<chat_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]

6. 检查Channels全局配置

确保settings.py中正确配置Channels:

INSTALLED_APPS = [
    # 其他应用
    'channels',
    'chat',
]

ASGI_APPLICATION = 'djangochat.asgi.application'

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer',
        # 生产环境建议替换为Redis
        # 'BACKEND': 'channels_redis.core.RedisChannelLayer',
        # 'CONFIG': {
        #     "hosts": [('127.0.0.1', 6379)],
        # },
    },
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 07:07:53