目录

Websocket 实现扫码二维码登录

首先简单的讲一下二维码实现登录的步骤: 1、网页端向服务端请求二维码,服务端生成一个二维码提供给网页端; 这个二维码其实是一个地址 2、客户端通过扫描网页端的二维码,跳转到二维码指向的地址, 然后通过鉴权验证机制,通知服务端鉴权的结果 3、客户端端根据不同的鉴权结果通知网页端做出相应的动作

对于步骤有了简单的概念之后, 我们可以显而易见的看到整个流程中主要需要解决的主要是两个问题:

1、如果处理用户的授权机制,保证用户权限的安全性

2、如何让网页端根据用户在客户端上的选择及时的做出不同的响应

针对第一个问题, 一方面,我们在生成二维码的时候,会同时生成一个唯一的session_id的标志,然后将其加入到二维码的url中, 当客户端扫描二维码跳转到指定地址的时候,能够根据session_id保证这个二维码是我们自己生成的; 另一方面, 客户端跳转过来的时候,会同时带上用户的id和用户的token, 通过id和token的验证保证用户的有效性,同时也能讲对应的session_id和用户id绑定,通知网页端是哪个用户扫描了这个二维码。

另外,多讲一点, 客户端扫描的同时带上来的用户id和用户的token,在实际使用的时候,我们是用户在登录的时候通过OAuth2.0的鉴权逻辑获取的, 所以能直接通过token验证安全性。

那么第二个问题, 如何及时的通知网页端做出变化呢。 这边一开始有两个方案: 一个是网页端采用轮询的机制,间隔一小段时间来询问服务端, 是否有用户扫描了这个二维码,以及扫描的结果; 另一个方案就是网页端在请求二维码的时候发起的就是一个websocket请求,这样网页端和服务端就能一直保持连接, 在客户端扫描二维码之后, 服务端就能够及时的通知到网页端,做到实时化。

两者的优缺点相信也比较明显, 在实际的开发过程中,我们采用了websocket的方式。 这边其实还有一个问题, 用户在客户端扫描二维码并授权了之后,如果通知到对应的网页端呢。 在网页端请求二维码的同时,我们会将当前的请求以session_id为key的方式进行注册, 当客户端扫描或者授权的时候,会将这个session_id带上来, 根据这个session_id就可以取到对应的请求,然后发回操作结果给网页端。

tornado websocket实现二维码扫描登录

网页端请求二维码

@router.Route('qrcode', name='ConnectQRcode')
class ConnectQRcode(tornado.websocket.WebSocketHandler):
    @tornado.gen.coroutine
    def open(self):
    	 #生成唯一的id,并带在url中
        req_id = id(self)
        url = "https://xxxxxxx?uid=" + uid + "&req_id=" + str(req_id)
        # 根据URL生成对应的二维码
        q = qrcode.main.QRCode()
        q.add_data(url)
        q.make()
        m = q.make_image()
        png_name = "static/" + uid + ".png"
        m.save(png_name)
        # 注册回调函数
        self.application.cart.register(req_id, self.callback)
        # 返回二维码
        self.write_message("https://xxxx/" + png_name)


    def on_close(self):
    	# 关闭的时候删除掉注册的回调函数
        self.application.cart.unregister(id(self), self.callback)
        logging.info("remove register")
        logging.info('WebSocket closed')

    def callback(self, event, extra_info):
        # 回调函数具体的操作
        self.write_message(xxxx)

客户端扫描二维码

@router.Route('/qrscan', name='qrscan')
class ConnectQRscan(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        try:
            # 得到本次请求的req_id, 然后根据req_id调用对应的回调函数
            req_id = self.get_argument('req_id', strip=False)
            self.application.cart.notify(req_id, "scan", {'req_id': req_id})
            ret = {}
        except Exception as e:
            logging.error("qrscan error: %s" % e)
            ret = {'errorno': -1, 'errormsg': "Error", 'data': None}
        self.write(ret)