django websocket使用JWT登陸驗(yàn)證

使用channels實(shí)現(xiàn)websocket伤提,利用restframework-jwt功能進(jìn)行token驗(yàn)證衅谷。
流程如下:

Http登陸-->獲取token-->websocket請(qǐng)求連接攜帶token-->自定義channels的Authentication-->驗(yàn)證token-->允許接入

自定義channels的Authentication官方鏈接

下面的是rest_framework_jwt(不是channels)的JSONWebTokenAuthentication默認(rèn)的token驗(yàn)證方法, 此方法是同步的涝缝,還執(zhí)行了user = self.authenticate_credentials(payload)數(shù)據(jù)庫(kù)的查詢畅铭,這些操作都要修改為異步的(channels的異步才能盡其能)

    def authenticate(self, request):
        """
        Returns a two-tuple of `User` and token if a valid signature has been
        supplied using JWT-based authentication.  Otherwise returns `None`.
        """
        jwt_value = self.get_jwt_value(request)
        if jwt_value is None:
            return None

        try:
            payload = jwt_decode_handler(jwt_value)
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()

        user = self.authenticate_credentials(payload)

        return (user, jwt_value)

上面的代碼是從request中獲取token的菠隆,但websocket提供的是scope兵琳。
下面是channels的官方樣例的權(quán)限驗(yàn)證的中間件。

class QueryAuthMiddleware:
    """
    Custom middleware (insecure) that takes user IDs from the query string.
    """

    def __init__(self, inner):
        # Store the ASGI application we were passed
        self.inner = inner

    def __call__(self, scope):

        # Close old database connections to prevent usage of timed out connections
        #close_old_connections()

        # Look up user from query string (you should also do things like
        # checking if it is a valid user ID, or if scope["user"] is already
        # populated).
        
        user = User.objects.get(id=int(scope["query_string"]))

        # Return the inner application directly and let it run everything else
        return self.inner(dict(scope, user=user))

從上面的JWT和自定義授權(quán)類中得知骇径,支持websocket的JWT驗(yàn)證要做的兩件事:

  • 從socpe中獲取token
  • 調(diào)用JSONWebTokenAuthentication驗(yàn)證Token躯肌,并獲取用戶

從channels的官方文檔說(shuō)明中,執(zhí)行數(shù)據(jù)庫(kù)的查詢要使用channels.db.database_sync_to_async 破衔,即是JSONWebTokenAuthentication類中的authenticate方法要變?yōu)楫惒角迮移鋬?nèi)部調(diào)用的用戶查詢也得是database_sync_to_async


修改代碼晰筛,支持異步的websocket支持JWT如下:

新增WebsocketTokenAuthentication類(繼承JSONWebTokenAuthentication)嫡丙,修改部分有:

  • 從scope中獲取url中的token。
  • 修改其用戶數(shù)據(jù)庫(kù)查詢代碼读第,支持異步曙博。
class WebsocketTokenAuthentication(JSONWebTokenAuthentication):
    """
    支持channels中間件的token處理和驗(yàn)證。
    """

    def get_jwt_value(self, scope):
        """
        websocket的url:/ws/chat/<str:room_name>?token
        token格式: 'JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJ'
        """
        token = str(scope['query_string'].decode('utf-8'))
        token = urllib.parse.unquote(token)
        token = token.split(" ")[1].encode('utf-8')
        return token
    
    async def authenticate_credentials(self, payload):
        """
        覆蓋此方法的user查詢卦方,使得其支持異步羊瘩。
        Returns an active user that matches the payload's user id and email.
        """
        User = get_user_model()
        username = jwt_get_username_from_payload(payload)

        if not username:
            msg = _('Invalid payload.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            # user = User.objects.get_by_natural_key(username)
            user = await self._get_user_by_natural_key(User, username)
        except User.DoesNotExist:
            msg = _('Invalid signature.')
            raise exceptions.AuthenticationFailed(msg)

        if not user.is_active:
            msg = _('User account is disabled.')
            raise exceptions.AuthenticationFailed(msg)

        return user
    
    @database_sync_to_async
    def _get_user_by_natural_key(self, User, username):
        return User.objects.get_by_natural_key(username)

下面就是創(chuàng)建一個(gè)QueryAuthMiddleware中間件泰佳,從WebsocketTokenAuthentication驗(yàn)證token 并獲取用戶盼砍。

class QueryAuthMiddleware:
    """
    Custom middleware (insecure) that takes user IDs from the query string.
    """

    def __init__(self, inner):
        # Store the ASGI application we were passed
        self.inner = inner

    def __call__(self, scope):

        # Close old database connections to prevent usage of timed out connections
        # close_old_connections()

        # Look up user from query string (you should also do things like
        # checking if it is a valid user ID, or if scope["user"] is already
        # populated).
        
        auth = WebsocketTokenAuthentication()
        user, token = auth.authenticate(scope)

        # Return the inner application directly and let it run everything else
        return self.inner(dict(scope, user=user))

在routing.py中注冊(cè)中間件

application = ProtocolTypeRouter({
    # 'websocket': AuthMiddlewareStack(
    #     URLRouter(
    #         chat.routing.websocket_urlpatterns
    #     )
    # ),
    'websocket': QueryAuthMiddleware(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})

至此尘吗,修改完畢。
使得可以使用AsyncWebsocketConsumer的Consumer和支持JWT驗(yàn)證浇坐。
后面只需要在consumer中驗(yàn)證是否存在用戶睬捶,驗(yàn)證則accept。

class ChatConsumer(AsyncWebsocketConsumer):

    async def connect(self):

        user = await self.scope['user']
        if not user:
            await self.close()
        else:
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            self.room_group_name = 'chat_%s' % self.room_name

            # Join room group
            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name
            )
            await self.accept()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末近刘,一起剝皮案震驚了整個(gè)濱河市擒贸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌觉渴,老刑警劉巖介劫,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異案淋,居然都是意外死亡座韵,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門踢京,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)誉碴,“玉大人,你說(shuō)我怎么就攤上這事瓣距∏粒” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蹈丸,是天一觀的道長(zhǎng)成黄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)逻杖,這世上最難降的妖魔是什么慨默? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮弧腥,結(jié)果婚禮上厦取,老公的妹妹穿的比我還像新娘。我一直安慰自己管搪,他們只是感情好虾攻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著更鲁,像睡著了一般霎箍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澡为,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天漂坏,我揣著相機(jī)與錄音,去河邊找鬼。 笑死顶别,一個(gè)胖子當(dāng)著我的面吹牛谷徙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驯绎,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼完慧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了剩失?” 一聲冷哼從身側(cè)響起屈尼,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拴孤,沒想到半個(gè)月后脾歧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡演熟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年涨椒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绽媒。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚕冬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出是辕,到底是詐尸還是另有隱情囤热,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布获三,位于F島的核電站旁蔼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疙教。R本人自食惡果不足惜棺聊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贞谓。 院中可真熱鬧限佩,春花似錦、人聲如沸裸弦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)理疙。三九已至晕城,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窖贤,已是汗流浹背砖顷。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工贰锁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滤蝠。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓豌熄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親几睛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容