使用令牌身份验证时如何在 django 通道中的 websocket 连接中对用户进行身份验证
How to authenticate a user in websocket connection in django channels when using token authentication
我正在使用前端框架 (Vuejs) and django-rest-framework for the REST API in my project. Also, for JSON web token authentication I am using django-rest-framework-jwt。成功登录后,系统会向用户提供一个令牌。此令牌会传递到每个请求中以获取任何 API 相关内容。
现在我想将 django channels 集成到我的项目中。因此,登录成功后,当客户端收到令牌时,我想启动一个 websocket 连接。然后在服务器(消费者)上,我想检查请求的用户是否不是匿名的。如果请求的用户是匿名的,我想关闭连接或者接受它。
到目前为止我是这样的:
客户端:
const socket = new WebSocket("ws://" + "dev.site.com"+ "/chat/");
routing.py:
channel_routing = [
route("websocket.connect", ws_connect),
...
...
]
消费者:
def ws_connect(message):
# if the user is no anonymous
message.reply_channel.send({
"accept": True
})
# else
message.reply_channel.send({
"close": True
})
在 documentation 中有一个装饰器 @channel_session_user_from_http
,它将提供一个 message.user
。但我使用的是令牌而不是会话。使用令牌身份验证时如何检查连接用户,以便我可以接受或关闭连接。或者,如果有更好的方法,请告诉我。谢谢。
问题是浏览器不支持在websocket升级时通过jwt auth headers,所以基本上就是这样。前段时间我遇到了这个问题,并想出了通过查询参数传递令牌的解决方案 - 请注意,如果没有 TLS,这是完全不安全的,因为您在 URI 中公开了身份验证。我无法再访问确切的代码,但我的想法是:
from channels.generic.websockets import JsonWebsocketConsumer
from channels.handler import AsgiRequest
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from jwt.exceptions import InvalidTokenError
from rest_framework.exceptions import ValidationError
class Consumer(JsonWebsocketConsumer):
def connect(self, message, **kwargs):
# construct a fake http-like request object from the message
message.content.setdefault('method', 'FAKE')
request = AsgiRequest(message)
# validate the token
try:
VerifyJSONWebTokenSerializer().validate(request.GET)
super().connect(message, **kwargs)
except (KeyError, InvalidTokenError, ValidationError,):
# token is either not available or invalid
# so we disconnect the user
message.reply_channel.send({'close': True})
使用
注册消费者
channel_routing = [
...
route_class(Consumer, path=r'^my-ws-endpoint$'),
]
在浏览器端,您可以通过在 websocket URI 中将令牌作为查询参数传递来建立 websocket 连接:
let token: string = 'my-token'; // get the token
let wsHandler: $WebSocket = new $WebSocket('wss://example.com/my-ws-endpoint/?token=' + token, ...);
然后,您可以在类似于 @channel_session_user_from_http
的装饰器中提取身份验证检查代码,并仅装饰您的连接例程,或者如果您使用 class-based 路由,则将代码提取到 mixin。
我想重复一遍,如果不使用加密,这种方法是完全不安全的,所以在生产中你的 URI 应该以 https/wss
.
开头
编辑:这里有一个pretty nice solution用于DRF token auth,适用于function-based和class-based路由。它与我的方法几乎相同,构造一个请求 object 并将其传递给身份验证器。
@hoefling 的回答是我的向导。我对验证用户的两件事感到困惑。
令牌可以做什么?
- 您可以将令牌作为查询字符串传递并获取该查询参数。详细了解如何获取查询参数 here.
- 或者如果您已经在请求的授权中传递它 header,您可以从那里获取它,就像@hoefling 对他的回答所做的那样。请记住首先伪造该请求。
如何验证该令牌并获取用户?
- 最后
VerifyJSONWebTokenSerializer
class 是我验证令牌所需的全部,并获得该令牌的用户 object。 (感谢@hoefling!)您可以阅读 django-rest-framework-jwt here. 的实际代码
所以,我最终这样做了:
def ws_connect(message):
message.content.setdefault('method', 'FAKE')
django_request = AsgiRequest(message)
token = django_request.GET['token'].split(' ')[1]
try:
data = {'token': token}
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
...
...
message.reply_channel.send({
"accept": True
})
except (KeyError, InvalidTokenError, ValidationError,):
...
...
message.reply_channel.send({
"text": "Authentication error",
"close": True
})
我正在使用前端框架 (Vuejs) and django-rest-framework for the REST API in my project. Also, for JSON web token authentication I am using django-rest-framework-jwt。成功登录后,系统会向用户提供一个令牌。此令牌会传递到每个请求中以获取任何 API 相关内容。
现在我想将 django channels 集成到我的项目中。因此,登录成功后,当客户端收到令牌时,我想启动一个 websocket 连接。然后在服务器(消费者)上,我想检查请求的用户是否不是匿名的。如果请求的用户是匿名的,我想关闭连接或者接受它。
到目前为止我是这样的:
客户端:
const socket = new WebSocket("ws://" + "dev.site.com"+ "/chat/");
routing.py:
channel_routing = [
route("websocket.connect", ws_connect),
...
...
]
消费者:
def ws_connect(message):
# if the user is no anonymous
message.reply_channel.send({
"accept": True
})
# else
message.reply_channel.send({
"close": True
})
在 documentation 中有一个装饰器 @channel_session_user_from_http
,它将提供一个 message.user
。但我使用的是令牌而不是会话。使用令牌身份验证时如何检查连接用户,以便我可以接受或关闭连接。或者,如果有更好的方法,请告诉我。谢谢。
问题是浏览器不支持在websocket升级时通过jwt auth headers,所以基本上就是这样。前段时间我遇到了这个问题,并想出了通过查询参数传递令牌的解决方案 - 请注意,如果没有 TLS,这是完全不安全的,因为您在 URI 中公开了身份验证。我无法再访问确切的代码,但我的想法是:
from channels.generic.websockets import JsonWebsocketConsumer
from channels.handler import AsgiRequest
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from jwt.exceptions import InvalidTokenError
from rest_framework.exceptions import ValidationError
class Consumer(JsonWebsocketConsumer):
def connect(self, message, **kwargs):
# construct a fake http-like request object from the message
message.content.setdefault('method', 'FAKE')
request = AsgiRequest(message)
# validate the token
try:
VerifyJSONWebTokenSerializer().validate(request.GET)
super().connect(message, **kwargs)
except (KeyError, InvalidTokenError, ValidationError,):
# token is either not available or invalid
# so we disconnect the user
message.reply_channel.send({'close': True})
使用
注册消费者channel_routing = [
...
route_class(Consumer, path=r'^my-ws-endpoint$'),
]
在浏览器端,您可以通过在 websocket URI 中将令牌作为查询参数传递来建立 websocket 连接:
let token: string = 'my-token'; // get the token
let wsHandler: $WebSocket = new $WebSocket('wss://example.com/my-ws-endpoint/?token=' + token, ...);
然后,您可以在类似于 @channel_session_user_from_http
的装饰器中提取身份验证检查代码,并仅装饰您的连接例程,或者如果您使用 class-based 路由,则将代码提取到 mixin。
我想重复一遍,如果不使用加密,这种方法是完全不安全的,所以在生产中你的 URI 应该以 https/wss
.
编辑:这里有一个pretty nice solution用于DRF token auth,适用于function-based和class-based路由。它与我的方法几乎相同,构造一个请求 object 并将其传递给身份验证器。
@hoefling 的回答是我的向导。我对验证用户的两件事感到困惑。
令牌可以做什么?
- 您可以将令牌作为查询字符串传递并获取该查询参数。详细了解如何获取查询参数 here.
- 或者如果您已经在请求的授权中传递它 header,您可以从那里获取它,就像@hoefling 对他的回答所做的那样。请记住首先伪造该请求。
如何验证该令牌并获取用户?
- 最后
VerifyJSONWebTokenSerializer
class 是我验证令牌所需的全部,并获得该令牌的用户 object。 (感谢@hoefling!)您可以阅读 django-rest-framework-jwt here. 的实际代码
- 最后
所以,我最终这样做了:
def ws_connect(message):
message.content.setdefault('method', 'FAKE')
django_request = AsgiRequest(message)
token = django_request.GET['token'].split(' ')[1]
try:
data = {'token': token}
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
...
...
message.reply_channel.send({
"accept": True
})
except (KeyError, InvalidTokenError, ValidationError,):
...
...
message.reply_channel.send({
"text": "Authentication error",
"close": True
})