网页扫码登录原理及过程
现实中我们经常会用上扫码登录的方式,可以借助微信、qq等oauth2认证方式,也可以通过APP
实现扫码登录。那么现在我们介绍下通过app实现扫码登录的一系列原理,及实现的过程。
扫码的过程分析
- 打开登录页面,展示一个二维码(web)
- 打开APP扫描该二维码后,APP显示确认、取消按钮(app)
- 这时候登录页面展示被扫描的用户头像等信息(web)
- 用户在APP上点击确认登录(app)
- 页面登录成功,并进入主应用程序页面(web)
原理分析
- 页面展示的二维码内容(包含一个ticket或者叫token也行),在后台临时缓存中很重要
- 该ticket是关联扫码人信息及后台用户库的关键
安全性分析
- 该ticket是随机生成、可以是随机串、url等。只要保证其内容唯一即可。
- 肯定是登录APP后才可以扫描,所以接收扫描结果的通道要对APP安全。防止第三方模拟请求(关键)。
实现过程
1. 开发一个登录页面,支持用户名、密码登录、同时可以切换成二维码登录
2. 当切换成二维码登录时,从后端接口获取一个ticket生成二维码,并且开始从后台轮训扫描结果
返回结果可以是单独ticket串,也可以是url(方便通过其他扫码工具扫描后转向公司宣传页面)
每隔1~2秒轮循环后台扫描结果。
3. 后端返回ticket的的接口过程
- 创建一个2分钟有效的唯一ticket串(全局唯一随机码),可以借助redis缓存来做时效性控制。
- 存储redis中键为ticket值,value默认为unknown表示等待扫描(app扫描会改变value)
- 把生成的ticket放入session域中,这样轮训状态接口就可以不附带ticket(进一步增强了安全性)
代码参考:1
2
3
4
5
6
7
8
9
10
11
12
13
"/qrcode/generate",method = RequestMethod.POST) (value =
public Map<String,String> generateQrCode(HttpServletRequest request){
String qrcode = qrcodeService.createQrcode();
//二维码登录ticket 放入session中
request.getSession().setAttribute(QRCODE_TICKET_KEY,qrcode);
Map<String,String> map = new HashMap<>();
map.put(QRCODE_TICKET_KEY,domainUrl+"ierp/qrcode/redirect?q="+qrcode);
return map;
}
$qrcodeService#createQrcode()
1 | /** |
4. 后端轮询扫描状态的接口详解
- 页面轮询接口不需要向服务器传参,借助session域中的ticket值来向后端获取扫描结果
- 二维码存在多种状态
- 二维码超时,状态为-2,获取的value为空,提示无效的二维码。
- 二维码待扫描,状态为-1,获取的value为初始值unknown。
- 二维码扫描成功,未确认登录,状态为0,获取的value为包含用户信息的json串,confirm 为false。(获取该状态后可以展示扫描用户的头像)
- 二维码扫描陈工,确认登录,状态为1,获取的value为包含用户信息的json串,confirm 为true。(获取该状态后可以提交之前,搞个过渡动画)
参考代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/**
* 获取二维码的扫码状态
* @param qrCodeTicket
* @return
*/
public QrcodeStatus getQrcodeStatus(String qrCodeTicket){
String value = redisTemplate.boundValueOps(QRCODE_REDIS_KEY_PREFIX + qrCodeTicket).get();
if(value==null||"".equals(value)){
return new QrcodeStatus(-2,"无效的二维码");
}else if(value.equals("unknown")){
return new QrcodeStatus(-1,"待扫描的二维码");
}else{
try {
JsonNode jsonNode = objectMapper.readValue(value, JsonNode.class);
String accountName = jsonNode.path("accountName").asText();
String accountPic = jsonNode.path("accountPic").asText();
boolean confirm = jsonNode.path("confirm").asBoolean();
if(confirm){
return new QrcodeStatus(1,accountName,accountPic,"确认登录");
}else {
return new QrcodeStatus(0,accountName,accountPic,"未确认登录,用来显示头像");
}
} catch (IOException e) {
e.printStackTrace();
}
return new QrcodeStatus(-3,"未知错误");
}
}
5. APP扫描二维码及点击确认登录的后台接口(注意该接口只开放给app调用,本示例是后端api,其实前面还有一层app网关代理,已经做了相应的令牌校验、sign校验)
- app扫描的前提必须已经登录。
- app扫描网页二维码,向后台请求接口,传入用户标识、扫描状态(确认状态)、ticket值
- 后台判断app提交过来的参数,并校验后,更新redis缓存中ticket的value(存入用户信息json及confirm状态)。
- 校验失败的相关错误信息要在app端提示出来的。
1 | "扫码登录") ( |
6. 用户在app端确认登录后,前端网页轮询到该结果,自动提交登录请求
- 登录提交后,后台判断如果是如果二维码登录,从session域中获取ticket值,并从根据ticket获取redis缓存中的用户信息。
- 得到用户信息后,就可以执行正常的登录校验流程。
7. 彩蛋:就是当生成的二维码是一个包含ticket值的url的时候。
- app扫描该二维码,需要解析出ticket再执行扫描结果逻辑
- 如果是其他app扫描该二维码,会访问该url地址,这个时候你就可以做一道重定向到其他你想让用户看到的地址。
1 | "/qrcode/redirect") ( |
那么这个二维码就会即起到了扫码登陆的作用,也起到了扫描下载app,或者进入宣传网站的作用。可谓一举多得。