2017-02-04 30 views
12

試圖通過一個非常簡單的websockets應用程序來獲取使用Django渠道的認證,該應用程序可以回傳用戶通過前綴"You said: "發送的任何內容。使用Django渠道進行會話認證

我的流程:

web: gunicorn myproject.wsgi --log-file=- --pythonpath ./myproject 
realtime: daphne myproject.asgi:channel_layer --port 9090 --bind 0.0.0.0 -v 2 
reatime_worker: python manage.py runworker -v 2 

heroku local -e .env -p 8080本地測試時運行的所有進程,但你也可以單獨運行它們。

請注意我有localhost:8080上的WSGI和localhost:9090上的ASGI。

路由和消費者:

### routing.py ### 

from . import consumers 

channel_routing = { 
    'websocket.connect': consumers.ws_connect, 
    'websocket.receive': consumers.ws_receive, 
    'websocket.disconnect': consumers.ws_disconnect, 
} 

### consumers.py ### 

import traceback 

from django.http import HttpResponse 
from channels.handler import AsgiHandler 

from channels import Group 
from channels.sessions import channel_session 
from channels.auth import channel_session_user, channel_session_user_from_http 

from myproject import CustomLogger 
logger = CustomLogger(__name__) 

@channel_session_user_from_http 
def ws_connect(message): 
    logger.info("ws_connect: %s" % message.user.email) 
    message.reply_channel.send({"accept": True}) 
    message.channel_session['prefix'] = "You said" 
    # message.channel_session['django_user'] = message.user # tried doing this but it doesn't work... 

@channel_session_user_from_http 
def ws_receive(message, http_user=True): 
    try: 
     logger.info("1) User: %s" % message.user) 
     logger.info("2) Channel session fields: %s" % message.channel_session.__dict__) 
     logger.info("3) Anything at 'django_user' key? => %s" % (
      'django_user' in message.channel_session,)) 

     user = User.objects.get(pk=message.channel_session['_auth_user_id']) 
     logger.info(None, "4) ws_receive: %s" % user.email) 

     prefix = message.channel_session['prefix'] 

     message.reply_channel.send({ 
      'text' : "%s: %s" % (prefix, message['text']), 
     }) 
    except Exception: 
     logger.info("ERROR: %s" % traceback.format_exc()) 

@channel_session_user_from_http 
def ws_disconnect(message): 
    logger.info("ws_disconnect: %s" % message.__dict__) 
    message.reply_channel.send({ 
     'text' : "%s" % "Sad to see you go :(", 
    }) 

再進行試驗,我進入的相同域作爲我的HTTP網站的Javascript控制檯,並鍵入:

> var socket = new WebSocket('ws://localhost:9090/') 
> socket.onmessage = function(e) {console.log(e.data);} 
> socket.send("Testing testing 123") 
VM481:2 You said: Testing testing 123 

而我的本地服務器日誌顯示:

ws_connect: [email protected] 

1) User: AnonymousUser 
2) Channel session fields: {'_SessionBase__session_key': 'chnb79d91b43c6c9e1ca9a29856e00ab', 'modified': False, '_session_cache': {u'prefix': u'You said', u'_auth_user_hash': u'ca4cf77d8158689b2b6febf569244198b70d5531', u'_auth_user_backend': u'django.contrib.auth.backends.ModelBackend', u'_auth_user_id': u'1'}, 'accessed': True, 'model': <class 'django.contrib.sessions.models.Session'>, 'serializer': <class 'django.core.signing.JSONSerializer'>} 
3) Anything at 'django_user' key? => False 
4) ws_receive: [email protected] 

其中,當然,是沒有意義的。幾個問題:

  1. 爲什麼會Django的看到message.user作爲AnonymousUser,但有實際的用戶ID _auth_user_id=1(這是我正確的用戶ID)在會議?
  2. 我在8080上運行本地服務器(WSGI),在9090(不同端口)上運行daphne(ASGI)。我的WebSocket連接中沒有包含session_key=xxxx - 但Django能夠正確讀取我的瀏覽器的cookie,[email protected]According to Channels docs, this shouldn't be possible
  3. 在我的設置下,使用Django通道進行身份驗證的最佳/最簡單的方法是什麼?

回答

0

要回答你的第一個問題,你需要使用:

channel_session_user 

裝飾在接收和斷開呼叫。

channel_session_user_from_http 

在connect方法期間調用transfer_user會話將http會話轉移到通道會話。這樣所有將來的呼叫都可以訪問頻道會話來檢索用戶信息。

對於你的第二個問題,我相信你所看到的是默認的web套接字庫通過連接傳遞瀏覽器cookie。

第三,我認爲一旦改變了裝飾器,你的設置就會工作得很好。

+1

嘗試這個 - 不會改變任何東西。我仍然收到完全相同的'AnonymousUser'對象。 – lollercoaster

+0

使用'@ http_session_user'我可以得到正確的'message.user'對象,但是屬性'message.channel_session'不存在。 – lollercoaster

14

注:這個答案是明確的,以channels 1.xchannels 2.x使用different auth mechanism


我的日子不好過Django的渠道太多,我不得不深入到源代碼,以更好地理解文檔...

問題1:

的文檔提到這種裝飾互爲依託(http_sessionhttp_session_user ...),您可以使用來包裝你的消息的消費者,在這條道路是中間的一長串指出這一點:

現在,需要注意的一點是,在WebSocket連接的連接消息期間,您只能獲得詳細的HTTP信息(您可以在ASGI規範中瞭解更多信息) - 這意味着我們不會浪費帶寬通過Web服務發送相同信息不必要的電線。 這也意味着我們必須搶在連接處理器的用戶,然後將其存儲在會話; ....

它很容易迷失在all that,至少我們都沒有...

你一定要記住,這個發生在你使用channel_session_user_from_http

  1. 它調用http_session_user
    一個。調用http_session它將解析該消息並給我們一個message.http_session屬性。 b。在從調用返回時,它啓動基於它在message.http_session獲得的信息(稍後會咬你)
  2. 它調用channel_session將在message.channel_session啓動虛擬會議和它關係到郵件回覆通道message.user
  3. 現在它調用transfer_user這將http_session移動到channel_session

這發生在一個網頁套接字的連接處理過程中,所以在後續的消息,你不會有存取權限的詳細HTTP信息,所以發生了什麼之後連接是,你再次打電話channel_session_user_from_http,在這種情況下(後連接消息)調用http_session_user這將嘗試讀取Http信息,但失敗導致setting message.http_session to None and overriding message.user to AnonymousUser
這就是爲什麼你需要在這種情況下使用channel_session_user

問題2:

通道可以使用從餅乾Django的會話(如果你正在運行相同的端口您的主網站上的WebSocket的服務器,使用類似達芙妮),或從session_key可以GET參數,如果要通過WSGI服務器繼續運行HTTP請求並將WebSockets卸載到另一個端口上的第二個服務器進程,則該參數可用。

記住http_session,那個裝飾可以讓我們的message.http_session數據?看來,如果它沒有找到一個session_key GET參數它fails to settings.SESSION_COOKIE_NAME,這是常規的sessionid餅乾,所以無論你提供session_key與否,你還是會得到,如果你登錄連接,當然這種情況發生,只有當您ASGI和WSGI服務器在同一個域(在這種情況下127.0.0.1),the port difference doesn't matter

我認爲,醫生現在都試圖溝通,但沒有擴大的差別在於,具有不同的域您ASGIWSGI服務器時,由於Cookie是由域沒有端口受限你需要設置session_key GET參數。

由於缺乏解釋,我不得不在相同的端口和不同的端口上運行ASGI和WSGI,結果相同,我仍然獲得身份驗證,將一個服務器域更改爲127.0.0.2而不是127.0.0.1,身份驗證是不見了,請設置session_key get參數並且認證又回來了。

更新:一個整改的文檔段是just pushed到渠道回購,它的意思是提到域名而不是像我提到的端口。

問題3:

我的答案是一樣的turbotux的,但時間越長,你應該ws_receive和ws_disconnect使用@channel_session_user_from_http上ws_connect和@channel_session_user,從你顯示什麼沒有告訴,如果你這樣做,它不會工作更改,也許嘗試從您的接收消費者中刪除http_user=True?甚至你我懷疑它有自無證沒有影響,只打算由通用消費者使用...

希望這有助於!

+0

非常感謝,我完全被源代碼所掩蓋,以瞭解如何爲消費者編寫我的測試。現在我明白了 –

+1

幫助感覺很棒! – HassenPy