网上关于扫一扫登陆的地方越来越多,但是关于这个扫一扫的实现的文章却甚少,我在做这个工程之前,弄了许久都没有资料。其实这个可以用DWR来实现,DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJAX技术的网站。它可以允许在浏览器里的代码使用运行在WEB服务器上的JAVA函数,就像它就在浏览器里一样,拥有运行在WEB上但是不需要浏览器插件的好处。
当然,扫一扫也不是乱扫就能够登陆的,关键是你的手机有一定的用户信息,将其获取出来,送到PC端。本文是基于微信公众平台的扫一扫,只要用户利用微信里面的扫一扫功能,就能获取微信用户资料然后登陆。
首先,如何配置好DWR,已经在《【DWR】Helloworld》(点击打开链接)一文中说明,这里不再赘述如何准备好dwr.jar,放到工程里面的lib文件夹,主要是说明这个微信扫一扫登陆是如何实现的。
一、基本目标
1、首先有一个这样的页面waitforscan.jsp,里面就一个等待用户扫描的二维码,上面输出的信息只是为了调试:
2、用微信的扫一扫功能对准这个二维码一扫:
3、就会弹出授权页面,用户一点确定,手机与电脑同时跳转到登陆成功页面,登陆成功页面输出微信的所有信息。:P为了笔者的个人安全,这里就不展示那个登陆页面了~
二、基本思想
1、关键是利用Dwr与Servlet对Httpsession与ScriptSession的操作。Httpsession是打开浏览器之后的过程,用户打开浏览器到关闭浏览器的一个过程对应一个Httpsession,这样我们就能够知道到底是哪个用户的哪个浏览器需要跳转,把用户的微信信息写入哪个浏览器;ScriptSession就是当前页的ID,这样就可以知道手机扫描二维码成功之后,能够告知waitforscan.jsp需要跳转。如果用户刷新此页,则Httpsession不改变,ScriptSession改变。
2、本工程的目录结构如下,记得在Servlet与Dwr需要的包,与Oauth认证需要的json解析包,具体可以看我《【Servlet】最简单的Servlet JavaWeb程序》(点击打开链接)、《【DWR】Helloworld》(点击打开链接)与《【Servlet】基于Jsp的微信Oauth2认证》(点击打开链接):
3、数据流程图如下:
4、数据流图如下:
三、制作过程
1、Web.xml
指明/scanhandle对应wx.loginqrcode.Scanhandle这个Java,同时启动Dwr,具体还可以参考我之前的《【Servlet】最简单的Servlet JavaWeb程序》(点击打开链接)与《【DWR】Helloworld》(点击打开链接)
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- 我需要用到dwr技术 --> <servlet> <servlet-name>dwr-invoker</servlet-name> <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>pollAndCometEnabled</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>crossDomainSessionSecurity</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> <!-- 微信扫二维码登陆部分--> <servlet> <servlet-name>scanhandle</servlet-name> <servlet-class>wx.loginqrcode.Scanhandle</servlet-class> </servlet> <servlet-mapping> <servlet-name>scanhandle</servlet-name> <url-pattern>/scanhandle</url-pattern> </servlet-mapping> </web-app>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.ltd.uk/dwr/dwr20.dtd"> <dwr> <allow> <create creator="new" javascript="dwrfuc" scope="application"> <param name="class" value="wx.loginqrcode.Dwr"/> </create> </allow> </dwr>
3、Dwr.java
这个java是整个工程的核心,请比对后面的jsp页面与java进行查看
package wx.loginqrcode; import java.util.*; import javax.servlet.http.*; import org.directwebremoting.*; import org.directwebremoting.proxy.dwr.*; public class Dwr { //创造两个MAP数据结构,一个是存放标识+ScriptSession,另一个是标识+HttpSession,HttpSession要被其他java操作,所以是public private static Map<String, ScriptSession> scrSessionMap = new HashMap<String, ScriptSession>(); public static Map<String, HttpSession> httpSessionMap = new HashMap<String, HttpSession>(); //这个标识就是httpSessionId public void getwebindex(String httpSessionId) { //Dwr通过以下的两种方式取得ScriptSession与HttpSession ScriptSession sessions = WebContextFactory.get().getScriptSession(); HttpSession httpSession = WebContextFactory.get().getHttpServletRequest().getSession(); httpSessionMap.put(httpSessionId, httpSession); scrSessionMap.put(httpSessionId, sessions); //再把HttpSession打回给waitforscan.jsp,这个HttpSession与waitforscan.jsp里面利用jsp生成的HttpSession相同 Util utilAll = new Util(sessions); utilAll.addFunctionCall("recid", httpSessionId); } public static void send(String to, String msg) { //这里是对于Scanhandle.java传过来消息进行处理,其实无论传递过来什么,都可以告诉waitforscan.jsp要跳转,这个msg是没有意义的 ScriptSession sessions = scrSessionMap.get(to); Util utilAll = new Util(sessions); utilAll.addFunctionCall("ifscanmsgrec", msg); } }
4、waitforscan.jsp
这一页用到了Jquery与Jquery_qrcode_master插件生成二维码,所以大家对比于上面的工程目录看到我的有js文件夹,关于如何用到Jquery与Jquery_qrcode_master插件生成二维码,可以参考《【jQuery】使用jquery-qrcode插件把网址转化成二维码,手机扫一扫即可访问》(点击打开链接)这里不赘述,很简单的,注意有兼容性的判断,二维码必须在上下左右留些空位,否则,用户扫描很困难,即使你的二维码成功,手机也是很难识别的。这页,甚至一下的页面应该对照着Dwr.java查看,因为Dwr.java是这个工程的核心
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <!-- 首先要引入3个在src文件夹里面的js文件 --> <script type="text/javascript" src="js/jquery-1.11.1.js"></script> <script type="text/javascript" src="js/jquery.qrcode.js"></script> <script type="text/javascript" src="js/qrcode.js"></script> <!-- 我需要用到dwr技术 --> <script type='text/javascript' src='../dwr/engine.js'></script> <script type='text/javascript' src='../dwr/util.js'></script> <!-- 注意这里,因为刚才在dwr.xml写了javascript="dwrfuc"一句,所以这里必须引入dwrfun.js,上面则是指定动作 --> <script type='text/javascript' src='../dwr/interface/dwrfuc.js'></script> <title>欢迎扫码登陆</title> </head> <body> 欢迎扫码登陆:你此次会话的HttpSessionID为:<span id="debug"></span> <!-- 新建一个id为qrcodeTable图层给这个二维码使用 --> <div id="qrcodeTable" style="margin-left:50px;margin-top:50px"></div> </body> </html> <!-- 再于脚本处加入如下代码即可 --> <script> //利用jsp生成一个HttpSessionID并且发送到dwr的getwebindex注册 var httpSessionId = "<%=request.getSession().getId()%>"; window.onload = function() { dwr.engine.setActiveReverseAjax(true); dwrfuc.getwebindex(httpSessionId); } function recid(dwrmsg){ document.getElementById("debug").innerHTML = httpSessionId; //用Jquery_qrcode_master插件生成二维码对于谷歌浏览器必须用到canvas这种方式,否则生成的二维码会出错,无法扫描 //因此有了如下的二维码判断 var isChrome = navigator.userAgent.toLowerCase().match(/chrome/) != null; if (!isChrome) { jQuery('#qrcodeTable').qrcode({ render : "table", text : "http://192.168.0.1/wechattest/scanhandle?httpSessionId="+httpSessionId }); } else{ jQuery('#qrcodeTable').qrcode({ render : "canvas", text : "http://192.168.0.1/wechattest/scanhandle?httpSessionId="+httpSessionId }); } } //如果被扫描了,收到dwrmsg放过来的信息,马上跳转到welcome.jsp function ifscanmsgrec(dwrmsg){ window.location.href = "welcome.jsp"; } </script>
这里部分用到了Oauth认证,请参考《【Servlet】对基于Jsp的微信Oauth2认证的改进》(点击打开链接)
package wx.loginqrcode; //首先由于oauth.java在其他的包里面,所以我们要引入这个包 import wx.oauth.*; //io异常需要 import java.io.*; //servlet需要 import javax.servlet.*; import javax.servlet.http.*; public class Scanhandle extends HttpServlet { //没有下面这句eclipse会出警告 private static final long serialVersionUID = 1L; //微信Oauth认证用到了get方法 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //首先清空一下Session,以免多用户扫描时候Session冲突,然后请求httpSessionId这个参数 request.getSession().invalidate(); String httpSessionId = request.getParameter("httpSessionId"); /** * @param code 这是微信提供的东西code * @param jsonstring 用户信息json字符串 */ String code = request.getParameter("code"); String jsonstring = null; //如果没有code,就向微信提供的网址请求 //state里面还可以带上你需要拿到用户信息之后进一步处理的参数 if (code == null) { /** * @param appid 微信提供给你的appid * @param redirect_uri 你自己的服务器地址/工程名/本页的Servelt名字 * @param state 把我们的httpSessionID带到state中,待Oauth认证成功之后可以取回 */ response.sendRedirect("https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=http://192.168.0.1/wechattest/scanhandle&response_type=code&scope=snsapi_userinfo&state="+ httpSessionId +"#wechat_redirect"); } else { //如果扫描成功之后,需要用state参数更新httpSessionId,否则由于Java开头有String httpSessionId = request.getParameter("httpSessionId");这句,会导致httpSessionId为空 httpSessionId = request.getParameter("state"); //向Oauth.java拿到了用户信息之后,存到手机Session jsonstring = new Oauth().getUserinfo(code); request.getSession().setAttribute("jsonstring", jsonstring); //通过httpSessionId取得对应的pc_httpSession,把记录用户信息的json字符串压入这个httpSession,这样pc就能收到用户微信信息 HttpSession pc_httpSession = Dwr.httpSessionMap.get(httpSessionId); pc_httpSession.setAttribute("jsonstring", jsonstring); //然后发信息给waitforscan.jsp告诉其跳转,这里什么信息是不重要的,因为waitforscan.jsp根本没有对这个消息进行处理 Dwr.send(httpSessionId, jsonstring); //如果手机自己跳转 request.getRequestDispatcher("loginqrcode/welcome.jsp").forward( request, response); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
登陆成功页面,非常简单,就是取用户信息,然后利用相应的json吧将其擦写,输出,
为了避免恶意用户直接通过输入浏览器地址访问这样,进行了抛出异常处理
至于微信服务器返回的用户微信信息json字符串,可以参考其开发者文档(点击打开链接)
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ page import="com.alibaba.fastjson.*" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>欢迎登陆</title> </head> <body> <% String nickname = null; String headimgurl = null; try{ String jsonstring = request.getSession().getAttribute("jsonstring").toString(); JSONObject jobject = JSON.parseObject(jsonstring); nickname = jobject.getString("nickname"); headimgurl = jobject.getString("headimgurl"); }catch (Exception e) { out.print("请正常打开此页"); } %> 欢迎登陆<%=nickname %>,<img src="<%=headimgurl %>" /> </body> </html>