实现微信扫描二维码登陆网站

前提条件:
1,该网站需要进行微信OAuth2.0网页授权。

2,微信用户已绑定网站账号。

即为那种经过一次绑定后,直接点击公众号菜单就能进入到个人页面,无需再次登陆,这种绑定一般是利用openID实现,OpenID是微信唯一对应用户身份的标识,网站或应用可将此ID进行存储,便于用户下次登录时辨识其身份,或将其与用户在网站上或应用中的原有账号进行绑定。

流程:
PC端—>打开登陆页面—>请求获取uuid及二维码图片—>服务器相应结果返回二维码相关信息—>页面轮询检查二维码是否被扫描—>服务器返回检查结果,若已扫描成功则跳转页面,否则继续轮询检查。

微信端—>扫一扫二维码—>微信浏览器自动打开二维码中的链接,并带上code参数,通过该参数可获取openID—>服务器检查uuid有效性,并查询该openID是否已经绑定账号,返回登陆结果。

注:如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。 其流程为:当微信浏览器请求了授权页面,会带上code参数,通过该参数我们可以获取微信用户的openID,具体流程可查看微信开发官方文档:

http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html

文章内使用简单的Servlet容器,使用QRGen生成二维码,这是一款轻量级的QRCode二维码生成工具。

QRGen依赖包:

<dependency>
        <groupId>net.glxn.qrgen</groupId>
        <artifactId>javase</artifactId>
        <version>2.0</version>
</dependency>

代码部分:

二维码生成工具:

public class QrGenUtil {
    public static ByteArrayOutputStream createQrGen(String url) throws IOException {
        //如果有中文,可使用withCharset("UTF-8")方法
        //设置二维码url链接,图片宽度250*250,JPG类型
        return QRCode.from(url).withSize(250, 250).to(ImageType.JPG).stream();
    }
}

domain:

public class User {
/**
 * 账号名
 */
private String name;
/**
 * 密码
 */
private String password;
/**
 * 微信账号唯一标识
 */
private String openID;
//省略get,set方法
}
页面:
![1463989305341672.jpg](http://upload-images.jianshu.io/upload_images/3086262-d7990c15d004b7aa.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

页面重点代码:
获取到图片后PC端页面轮询检测二维码是否被扫描

<img id="QrGen" src="" />
<script type="text/javascript">
$(document).ready(function() {
var uuid;
$.get("/qrgen?cmd=showQrGen", function(data, status) {
var obj = eval("(" + data + ")");
//设置该uuid值
uuid = obj.uuid;
//设置二维码图片地址
$("#QrGen").attr("src", obj.img);
//检查验证登录
checkScan();
});
function checkScan() {
setInterval(function() {
$.get("/qrgenLogin/qrgen?cmd=checkScan&uuid=" + uuid,
function(data, status) {
if (data == "ok") {
//验证成功并重定向到welcome页面
window.location = "welcome.jsp";
}
});
},4000)
}
});
</script>

服务器需要生成一个二维码,并且使用UUID来保持唯一性,避免登陆信息混乱。

/**

  • 获取uuid及二维码图片地址
  • @param req
  • @param resp
  • @throws ServletException
  • @throws IOException
    */
    protected void showQrGen(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    //生成UUID随机数
    UUID randomUUID = UUID.randomUUID();
    //通过应用获取共享的uuid集合
    Map<String,User> map = (Map) req.getServletContext().getAttribute("UUID_MAP");
    if (map == null) {
    map = new HashMap<String,User>();
    req.getServletContext().setAttribute("UUID_MAP", map);
    }
    //把uuid放入map中
    map.put(randomUUID.toString(), null);
    //二维码图片扫描后的链接
    String url = "http://192.168.1.104:8080/login?cmd=loginByQrGen&uuid="+ randomUUID;
    //生成二维码图片
    ByteArrayOutputStream qrOut = QrGenUtil.createQrGen(url);
    String fileName = randomUUID+ ".jpg";
    OutputStream os = new FileOutputStream(new File(req.getServletContext().getRealPath("/temp"),fileName));
    os.write(qrOut.toByteArray());
    os.flush();
    os.close();
    //返回页面json字符串,uuid用于轮询检查时所带的参数, img用于页面图片显示
    String jsonStr = "{"uuid":"" + randomUUID + "","img":"" + "/temp/"+fileName + ""}";
    OutputStream outStream = resp.getOutputStream();
    outStream.write(jsonStr.getBytes());
    outStream.flush();
    outStream.close();
    }
    }
二维码中的请求链接:
当用户使用与网站账号绑定后的微信号扫描二维码的时候,会将uuid和唯一标识openID的用户进行绑定,这个时候,浏览器通过轮询查询到uid扫描记录,得到相关响应信息,客户端由此也进入一个新的页面。

/**

  • 手机端扫描二维码执行的方法
  • @param req
  • @param resp
  • @throws ServletException
  • @throws IOException
    */
    protected void loginByQrGen(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    // 获取二维码链接中的uuid
    String uuid = req.getParameter("uuid");
    // 通过应用获取共享的uuid集合
    Map uuidMap = (Map) req.getServletContext().getAttribute("UUID_MAP");
    // 如果集合内没有这个uuid,则响应结果
    if (uuidMap == null || !uuidMap.containsKey(uuid)) {
    resp.getOutputStream().write("二维码不存在或已失效!".getBytes());
    return;
    }
    // 根据微信传来的code来获取用户的openID
    String code = req.getParameter("code");
    try {
    String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=YOUR_APPID"
    + "&secret=YOUR_SECRTC"
    + "&grant_type=authorization_code" + "&code=" + code;
    Gson gson = new Gson();
    Map map = gson.fromJson(HttpUtil.get(url, "utf-8"),
    new TypeToken<Map>() {}.getType());
    Object openID = map.get("openid");
    if (openID != null && !"".equals(openID)) {
    // 通过openID获取user对象
    User user = dao.getUserByOpenId(openID.toString());
    if (user != null) {
    // 如果查询到某个user拥有该openID,则设置到map集合内
    uuidMap.put(uuid, user);
    // 并返回手机端扫描结果
    resp.getOutputStream().write("登陆成功!".getBytes());
    return;
    }
    }
    // 如果没有openID参数,或查询不到openID对应的user对象,则移除该uuid,并响应结果
    uuidMap.remove(uuid);
    resp.getOutputStream().write("你还未绑定,请关注微信号并绑定账号!并使用微信客户端扫描!".getBytes());
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
PC端进行轮询检查:

/**

  • PC端不断进行轮询检查的方法
  • @param req
  • @param resp
  • @throws ServletException
  • @throws IOException
    */
    protected void checkScan(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    //获取页面的uuid参数
    String uuid = req.getParameter("uuid");
    //通过应用获取共享的uuid集合
    Map map = (Map) req.getServletContext().getAttribute("UUID_MAP");
    if (map != null) {
    //查询该uuid是否存在,且二维码已经被扫描并匹配到账号
    if(map.containsKey(uuid)){
    User user = (User) map.get(uuid);
    if (user != null) {
    //从集合中移除
    map.remove(uuid);
    //设置登录信息
    req.getSession().setAttribute("USER_IN_SESSION", user);
    resp.getOutputStream().write("ok".getBytes());
    }else{
    resp.getOutputStream().write("native".getBytes());
    }
    }
    }
    }
无论是通过微信扫一扫还是网站自身的客户端扫码功能,都是需要一方登陆或绑定账号,在扫码的时候才可以获取到用户身份的唯一标识,并且所使用的二维码需要有唯一性,失效时并不是二维码图片失效,而是二维码对应的链接或uuid参数失效,二维码只是文字和超链接的图形化而已。只是在超时、网络断开、过期等情况,此前获得的uuid应失效,对检测过程形成有效的防护。

来源:
http://bbs.520it.com/forum.php?mod=viewthread&tid=268&extra=page%3D1
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容