很多网站都多出来了微信二维码扫描登录,微信官方也有提供网站应用微信登录,然后在DAOClOUD看到了他们的微信扫描登录跟其他地方不一样,于是研究了一下,发现是利用生成带参数的二维码和扫描带参数二维码事件来实现的。
下面是我的实现过程,用的是Tornado实现的。
用户登录实现
首先是实现用户登录功能,这里我为了偷懒,就没有用实际的用户登录认证,而是只匹配用户名
主要代码如下
首先创造1000个假用户(名)
for i in xrange(1000):
q.put(i)
users.append(dict(username=str(i),
openid=None))
然后是登录
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self):
username = self.get_argument('username')
for user in users:
if user['username'] == username:
self.set_cookie('user', username)
return self.redirect('/user', 302)
self.redirect('/', 302)
接下来是一个虚假的用户中心,主要是为了证明登录成功了
class UserCenterHandler(tornado.web.RequestHandler):
def get_current_user(self):
user = self.get_cookie('user')
for _user in users:
if _user['username'] == user:
return _user
return user
@tornado.web.authenticated
def get(self):
self.render('usercenter.html', user=self.current_user)
二维码扫描登录
能够进行正常的登录之后,接下来就是二维码的扫描登录了
当用户扫描二维码后,微信会推送扫描带参数二维码事件,而这个事件有两种,一种是已关注扫描,另一种是未关注扫描,但是这两种都会带上 scene_id 和 ticket 两个参数,为了识别到是哪个用户扫描的,所以需要将 scene_id 或者 ticket 与当前打开二维码扫描界面的用户绑定起来,所以我用了一种我觉得很蠢的方法来生成 scene_id
q = Queue.LifoQueue(1000)
我生成了1000个 scene_id 放到一个队列里,当用户打开登录页面时,从队列里获得一个 scene_id,当用户关闭登录页面时,释放 scene_id,并且入队,这样就保证了 scene_id 的复用和不重复,用户登录界面与服务器保持连接我用的是 WebSocket,实现如下
class WebSocketHandler(tornado.websocket.WebSocketHandler):
@tornado.gen.coroutine
def open(self):
print 'open'
wechat = WeChat(access_token)
qid = q.get()
qr = yield wechat.get_ticket(180, qid)
c = {
'client': self,
'qid': qid,
'ticket': qr['ticket'],
'uuid': str(uuid.uuid4()),
'expires': 180,
}
clients.append(c)
d = copy.copy(c)
del d['qid'], d['client']
self.write_message(json.dumps(dict(type='qr', data=d)))
def on_close(self):
for c in clients:
if c['client'] == self:
q.put(c['qid'])
clients.remove(c)
前端实现如下
var ws = new WebSocket('ws://' + window.location.host + '/ws');
var ws = new WebSocket('ws://' + window.location.host + '/ws');
$(function() {
ws.onopen = function() {
console.info('open');
var get_qr = {
type: 'get_qr'
}
ws.send(JSON.stringify(get_qr));
};
var t;
ws.onmessage = function (evt) {
var msg = JSON.parse(evt.data);
console.log(msg);
if (msg.type == 'qr') {
$('#wechat-qr-img').attr('src', 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' + msg.data.ticket)
var expires = msg.data.expires;
function run() {
console.info(expires);
expires--;
if (expires == 0) {
clearTimeout(t);
$('#wechat-qr-img').attr('src', '');
alert('二维码过期');
window.location.reload();
}
t = setTimeout(function(){run()}, 1000);
}
run();
} else if (msg.type == 'success_and_not_bind') {
clearTimeout(t);
$('#wechat-bind').slideDown();
$('#bind-openid').val(msg.data);
} else if (msg.type == 'success') {
$.cookie('user', msg.data);
window.location.href = '/user';
}
};
$('#wechat-bind-btn').click(function() {
var username = $('#bind-username').val();
var openid = $('#bind-openid').val();
$.post('/user/bind', {openid: openid, username: username})
.success(function(data) {
console.info(data);
if (data.status) {
window.location.href = '/user';
}
})
.fail(function(data) {
console.error(data.responseJSON);
});
});
});
因为 WebSocket 没法设置 cookies,所以我就把需要设置的 cookies 发到了浏览器,在前端设置 cookies
服务器端接收到微信扫描事件时的处理
if msg.event == 'scan':
reply = create_reply('扫描', msg)
for c in clients:
if c['qid'] == int(msg.scene_id):
for user in users:
if user['openid'] == msg.source:
c['client'].write_message(dict(type='success', data=user['username']))
break
else: c['client'].write_message(dict(type='success_and_not_bind', data=msg.source))
当用户第一次扫描,并没有绑定帐号时,要先绑定帐号
class UserBindHandler(tornado.web.RequestHandler):
def post(self):
username = self.get_argument('username')
openid = self.get_argument('openid')
for user in users:
if user['username'] == username:
user['openid'] = openid
data = {
'status': True
}
self.set_cookie('user', username)
break
else:
data = {
'status': False,
'err': '用户不存在'
}
self.set_status(403)
self.set_header('content-type', 'application/json')
for user in users:
if user['openid']:
print user
self.write(json.dumps(data))
结尾
以上只是为了实验利用微信扫描二维码登录的可行性
上述完整代码放在了gist里面,点这里可以看到完整的