网页扫码请求登录的逻辑原理与实现

引言

现实中经常会需要我们需要扫码授权登陆,有的时候是借助微信授权登陆,有的时候商户需要登陆某个特定的app,在该app中扫码登陆。那么我们今天就来分析一下扫码登陆,这背后究竟发生了怎么样的请求交互,以及是怎么实现的。
下面我们以微信为例,调了微信商户登陆平台这个页面进行分析:
https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F

针对微信网页进行分析

首先如图1,一进入页面之后会请求生成一个二维码。

网页扫码请求登录的逻辑原理与实现_第1张图片
图1.初始请求拿到二维码

针对一个请求,前台会多次有间隔地轮询,如图2,如图3。

网页扫码请求登录的逻辑原理与实现_第2张图片
图2.反复请求
网页扫码请求登录的逻辑原理与实现_第3张图片
图3.请求所带参数

请求的响应结果有 "wait scan" 和 “二维码过期” 两种情况,如图4,图5所示。

网页扫码请求登录的逻辑原理与实现_第4张图片
图4.有效期内请求返回的结果
网页扫码请求登录的逻辑原理与实现_第5张图片
图5.超过有效期返回的结果

在二维码过期后,点击刷新二维码,之后便会重新请求获取到二维码,再次的轮询请求后台结果,如图6所示。

网页扫码请求登录的逻辑原理与实现_第6张图片
图6.点击二维码进行刷新后的效果

仿照设计与实现

设计

考虑的点:

  1. 二维码生成与展示。

这里我们采用前端生成随机串,以便前端后期不断的轮询。具体随机串藏在二维码中生成接口可以参考我之前的博文——java生成QR二维码。

  1. 轮询间隔,后端对应的过期与超时等返回。

这里新版的微信登陆采用的是前端sleep,频繁请求后端。在之前没改版的时候采用的是长连接,一次请求由后端自行轮询。本文采用后端轮询的形式。

  1. APP扫码登陆。

APP扫码识别出了二维码中的随机串,应该告诉服务器验证成功,待web下一次轮询服务器的时候要返回相应的token和登陆成功等其他信息。

将这几个点结合在一起就有了图7。

网页扫码请求登录的逻辑原理与实现_第7张图片
图7.设计逻辑图

那么经过分析,我们得知后端至少要3个接口。分别生成二维码,给WEB轮询,和给APP请求。生成二维码的参考博主之前的博文,目前就不在这里重复。下面给出其他两个接口的实现。

实现

  1. 给WEB轮询接口
    采用递归的形式实现轮询。利用redis存储了前端生成的随机串,设置0为默认值

CONNECT_TIME_OUT("连接超时",2001),
  private static String DEAFULT_ID = "0";
 /**
     * 获取token
     *
     * @param webLoginDTO
     * @return
     */
    @PostMapping(value = "webLoginCode/ask")
    @ResponseBody
    public RestResponse> askWebLoginCode(@JsonParam WebLoginDTO webLoginDTO) {
        String webLoginCode = webLoginDTO.getWebLoginCode();
        Assert.notNull(webLoginCode, "传入的随机串为空");
        return RestResponse.ok(askWebLoginCodePolling(webLoginCode, MAX_RETRY));
    }

    /**
     * 轮询获取token
     *
     * @param webLoginCode
     * @param retry
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Map askWebLoginCodePolling(String webLoginCode, int retry) {
        if (retry == 0) {
            throw new FantuanRuntimeException(FantuanErrors.CONNECT_TIME_OUT.getMessage(),
                    FantuanErrors.CONNECT_TIME_OUT.getCode());
        }
        Map resultMaps = new HashMap<>();
        String varR = varPool.getVar(getWebLoginVarPool(webLoginCode));
        if (StringUtils.isBlank(varR)) {
            throw new FantuanRuntimeException("已经过期,请重新刷新二维码");
        }

        if (!DEAFULT_ID.equals(varR)) {
            Map maps = JsonUtil.stringToMap(varR);
            String varResult = MapUtils.getString(maps, "uid");
            User user = userService.selectById(Long.valueOf(varResult));
            String webAuthKey = user.getWebAuthKey();
            if (StringUtils.isBlank(webAuthKey)) {
                webAuthKey = RandomNumberUtil.creatUUID32();
                user.setWebAuthKey(webAuthKey);
                userService.updateById(user);
            }
            resultMaps.put("userName", user.getUsername());
            resultMaps.put("uid", varResult);
            resultMaps.put("token", webAuthKey);
            resultMaps.put("extraInfo", MapUtils.getObject(maps, "extraInfo"));
            return resultMaps;
        } else {
            //这里需要hold住链接
            try {
                Thread.sleep(1000);
                return askWebLoginCodePolling(webLoginCode, retry - 1);
            } catch (InterruptedException e) {
                log.error("", e);
            }
        }
        return resultMaps;
    }

2.给APP请求接口
APP扫码登陆后,会把一些有用的信息给传递过来。这里后端做成了一个map extraInfo去接收,到时候整个extraInfo会返回给WEB端。
这样子的好处,后端就是成了一个验证平台而已,需要的信息只要由APP和WEB端定义好即可。

 /**
     * app扫码登陆
     *
     * @param webLoginDTO
     */
    @PostMapping(value = "webLoginCode/check")
    @ResponseBody
    public RestResponse checkWebLoginCode(@JsonParam WebLoginDTO webLoginDTO) {
        Long uid = SecurityUtils.getLoginAccountId();
        if (uid <= 0) {
            throw new FantuanRuntimeException("请登陆");
        }

        Assert.notNull(webLoginDTO, "传入的对象不能为空");
        String webLoginCode = webLoginDTO.getWebLoginCode();
        Assert.notNull(webLoginCode, "传入的二维串随机码不能为空");
        Map result = new HashMap<>();
        if (StringUtils.isBlank(varPool.getVar(getWebLoginVarPool(webLoginCode)))){
            throw new FantuanRuntimeException("该二维码已经过期");
        }
        Map maps = new HashMap<>();
        maps.put("uid", uid);
        maps.put("extraInfo", webLoginDTO.getExtraInfo());
        varPool.setVar(getWebLoginVarPool(webLoginCode), JsonUtil.mapToJson(maps), 60, TimeUnit.SECONDS);
        return RestResponse.ok("执行完成了");
    }
  1. 另外给出部分的WEB端代码



最终web端的页面如图8所示。这里采用的是后端长连接的形式,所以不像是新版微信那样请求是断续的。当然要做成那样,只要后端的轮询上限改成1,前端加上一个短暂的sleep即可。

网页扫码请求登录的逻辑原理与实现_第8张图片
图8.最终结果

总结:

其实无论是扫码登陆,还是网页的扫码支付,其实本质上都是藏着一个长连接/长轮询去监听服务器的状态变化。毕竟回call或者扫码识别等都是通过服务器来校验的。

你可能感兴趣的:(网页扫码请求登录的逻辑原理与实现)