内网环境下微信扫码登录小结

一、需求:

网站需要接入微信扫码登录,但此网站仅能在内网环境下访问,仅网站服务器可以连接微信外网

二、遇到的问题:

1、图片需要联网:

  • 参考网页:微信网页扫码登录
    按照上述网站上的指南接入,在可访问外网的情况下可以使用,但是由于二维码的图片是需要浏览器从微信的服务器中获取的,在内网情况下无法拿到图片

  • 解决方案:可以将二维码图片爬取过来,放入登录页面的标签中
    首先访问网站:
    https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

    通过分析网页源代码可以得到二维码图片是放在一个img标签中,图片链接为一个随机uuid,因此通过正则匹配到地址后再访问该图片链接可以抓取到图片,随后可以通过服务器将图片写回到登陆页面的中:
    在这里插入图片描述

后台获取图片代码:

@RequestMapping(value = "getQrCode")
	public void getQrCode(HttpServletResponse response, HttpServletRequest request) throws IOException
	{
		try {
			byte[] image = userService.getQrCode(request); //将网页上的图片转成btye数组
			response.setContentType("image/jpeg");
			response.getOutputStream().write(image);
			response.addHeader("Content-Disposition","attachment;filename=image.jpg");
		}catch (Exception e) {
			e.printStackTrace();
		}
	}

getQrCode:
先访问https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
获取到图片链接之后,再访问图片链接codeUrl进行访问
ResponseEntity<byte[]> imgEntity = restTemplate.getForEntity(codeUrl, byte[].class);
前端img标签的src填我们封装好的转接接口名称即可显示二维码

2、:获取到图片之后,扫码访问无法与前台交互

  • 拿到了图片,发现也可以扫,手机微信也能正常识别二维码,但是点了确认登录之后,页面没有跳转到登陆页面,也没有任何反应。原因是原本微信提供了一个页面,该页面上有一些js函数,进行了一些判断(如是否扫码、取消还是成功、执行页面跳转并带上code参数进行下一步验证),由于我们只拿了图片,切断了这些步骤,自然无反应

  • 解决方案:分析了微信网页的js,发现有一个定时函数,该函数会定期去请求一个接口,该接口的uuid参数正是之前访问二维码生成的那个uuid
    如图所示:/connect/l/qrconnect?uuid=071x6Yhr0dWpH
    在这里插入图片描述

随后查看该接口的返回内容:
在这里插入图片描述
可以发现该接口返回的正是微信扫码登录最关键的code和一个错误码

随后分析微信网页上的js:

 <script>
            !function() {
                function a(a) {
                    var b = document.location.search || document.location.hash;
                    if (b) {
                        if (/\?/.test(b) && (b = b.split("?")[1]),
                        null == a)
                            return decodeURIComponent(b);
                        for (var c = b.split("&"), d = 0; d < c.length; d++)
                            if (c[d].substring(0, c[d].indexOf("=")) == a)
                                return decodeURIComponent(c[d].substring(c[d].indexOf("=") + 1))
                    }
                    return ""
                }
                function b(a) {
                    jQuery.ajax({
                        type: "GET",
                        url: p + "/connect/l/qrconnect?uuid=071x6Yhr0dWpHa1T" + (a ? "&last=" + a : ""),
                        dataType: "script",
                        cache: !1,
                        timeout: 6e4,
                        success: function(a, e, f) {
                            var g = window.wx_errcode;
                            switch (g) {
                            case 405:
                                var h = "http://172.17.250.142/jkpt/loadByWx";
                                h = h.replace(/&amp;/g, "&"),
                                h += (h.indexOf("?") > -1 ? "&" : "?") + "code=" + wx_code + "&state=";
                                var i = c("self_redirect");
                                if (d)
                                    if ("true" !== i && "false" !== i)
                                        try {
                                            document.domain = "qq.com";
                                            var j = window.top.location.host.toLowerCase();
                                            j && (window.location = h)
                                        } catch (k) {
                                            window.top.location = h
                                        }
                                    else if ("true" === i)
                                        try {
                                            window.location = h
                                        } catch (k) {
                                            window.top.location = h
                                        }
                                    else
                                        window.top.location = h;
                                else
                                    window.location = h;
                                break;
                            case 404:
                                jQuery(".js_status").hide(),
                                jQuery(".js_qr_img").hide(),
                                jQuery(".js_wx_after_scan").show(),
                                setTimeout(b, 100, g);
                                break;
                            case 403:
                                jQuery(".js_status").hide(),
                                jQuery(".js_qr_img").hide(),
                                jQuery(".js_wx_after_cancel").show(),
                                setTimeout(b, 2e3, g);
                                break;
                            case 402:
                            case 500:
                                window.location.reload();
                                break;
                            case 408:
                                setTimeout(b, 2e3)
                            }
                        },
                        error: function(a, c, d) {
                            var e = window.wx_errcode;
                            408 == e ? setTimeout(b, 5e3) : setTimeout(b, 5e3, e)
                        }
                    })
                }
                function c(a, b) {
                    b || (b = window.location.href),
                    a = a.replace(/[\[\]]/g, "\\$&");
                    var c = new RegExp("[?&]" + a + "(=([^&#]*)|&|#|$)")
                      , d = c.exec(b);
                    return d ? d[2] ? decodeURIComponent(d[2].replace(/\+/g, " ")) : "" : null
                }
                var d = window.top != window;
                if (!d) {
                    document.getElementsByClassName || (document.getElementsByClassName = function(a) {
                        for (var b = [], c = new RegExp("(^| )" + a + "( |$)"), d = document.getElementsByTagName("*"), e = 0, f = d.length; f > e; e++)
                            c.test(d[e].className) && b.push(d[e]);
                        return b
                    }
                    );
                    for (var e = document.getElementsByClassName("status"), f = 0, g = e.length; g > f; ++f) {
                        var h = e[f];
                        h.className = h.className + " normal"
                    }
                }
                var i = parseInt(a("styletype"), 10)
                  , j = parseInt(a("sizetype"), 10)
                  , k = a("bgcolor")
                  , l = NaN;
                if (1 !== i && 0 !== i && 1 === l && (i = 0),
                1 === i)
                    d ? document.body.className = document.body.className + " redesign-style_iframe" + (1 === j ? " redesign-style_iframe-small" : "") : document.body.className = document.body.className + "redesign-style_page",
                    k && (document.body.style.backgroundColor = k),
                    jQuery(".new-template").show();
                else {
                    if (d) {
                        var m = "";
                        "white" != m && (document.body.style.color = "#373737")
                    } else
                        document.body.style.backgroundColor = "#333333",
                        document.body.style.padding = "50px";
                    if (jQuery(".old-template").show(),
                    0 !== i) {
                        var n = "";
                        if (n) {
                            var o = document.createElement("link");
                            o.rel = "stylesheet",
                            o.href = n.replace(new RegExp("javascript:","gi"), ""),
                            document.getElementsByTagName("head")[0].appendChild(o)
                        }
                    }
                }
                var p = window.usenewdomain ? "https://lp.open.weixin.qq.com" : "https://long.open.weixin.qq.com";
                setTimeout(b, 100)
            }();
        </script>

关键代码段:
在这里插入图片描述
可以发现不同状态码有不同的执行逻辑:
405时会将code加入到ridirect_url中然后进行跳转
404表示已经扫描
403表示用户扫描然后按了取消
408则是初始状态,表示无操作

因此解决方案就是将这个接口也经由网站服务器进行一层转封,每次前端轮询这个接口,查询扫码状态,每次生成二维码图片时,也将uuid存入到session当中

代码:

	/**
	 * 请求是否已经扫码
	 * @param response
	 * @param request
	 * @throws IOException
	 */
	@RequestMapping(value = "getQrCodeResult")
	public @ResponseBody String getQrCodeResult(HttpServletResponse response, HttpServletRequest request,String last) throws IOException
	{
		try {
			Object uuid=request.getSession().getAttribute("codeUUID");
			if(uuid==null){
				return "window.wx_errcode=408;window.wx_code='';";
			}
			return  userService.getQrCodeResult(request,last);//否则给接口发送Get请求,获取最新的状态和code信息
		}catch (org.springframework.web.client.ResourceAccessException e1){

		} catch(Exception e) {
			e.printStackTrace();
		}
		return "window.wx_errcode=408;window.wx_code='';";
	}

这里注意到有个last参数,记得要带上,表示上一次的状态码,猜测微信在这里做了处理 可以减少请求的次数。随后将上述js代码放到登录页面中,一进到页面就启动接口定时任务 进行轮询,如果用户扫码或者取消扫码,通过此接口可以更新相应的状态

3、:/connect/l/qrconnect?uuid=071x6Yhr0dWp接口缓慢,页面状态更新不及时

  • 描述:由于我们是做了接口转发,因此状态更新有一些延时,这个需要权衡一下调用频率和轮询时间。此接口怀疑微信做了处理,当用户未进行操作时,此接口返回的状态码为408,此时从请求到结束大概需要10+秒的时间,而当用户扫了码之后,状态码变成了404,此时这个接口请求会变快,大概200ms左右。由于是通过定时器轮询,微信好像做了频率限制,因此当状态码变成404时,由于速度很快,此时我们定时轮询容易造成刷屏,此时状态码会变成666,而原生Js中没有处理此状态码的操作。此外,发现当ctrl+F5强刷网页时,定时函数好像偶尔不执行,导致扫码状态无法更新和跳转登录。

  • 解决问题:

    针对状态码变成了404,此时这个接口请求会变快造成刷屏的问题,可以加上last参数,加入之后,该请求会挂起直到用户有下一步操作,可以减少刷屏

    针对666状态码 不放心可以加入一个处理分支,当遇到666时提示用户刷新二维码,然后启动新一轮计时

    针对ctrl+F5强刷网页时,定时函数好像偶尔不执行,导致扫码状态无法更新的问题 目前我自己的解决方案是后台访问该接口时,加入超时时间,当超过一定时间直接返回给前台,因为怀疑就是该接口一开始需要十几秒的访问时间造成的

三、其他:

1如果用了spring security进行管理的话,上述接口都不要屏蔽,否则就无法获取结果了(被拦截了)
2、绕过spring security,后台登录:

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
SecurityContextHolder.getContext().setAuthentication(authentication);
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,SecurityContextHolder.getContext());

3、本文仅用于自己记录学习用,如需要转载,请注明出处。如有错漏,欢迎指正。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值