公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序

腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
我了解网站扫描二维码并实现自动登陆功能是从微信哪里看到的,该功能体验还不错,减少密码被盗的风险。那么一个普通的网站该如何实现此功能呢?本文将借助websocket+java实现这一功能。
实现思路
- 后台系统,提供生成带参数的二维码的接口,这个参数就是唯一值(场景值)
- 访问到登录网站页面 时,生成二维码
- 用户拿APP扫码后,APP直接将场景值中的唯一值和用户信息反馈给后台系统
- 后台系统将用户访问的唯一值信息记录到redis,key就是唯一值(场景值)
- 网站端做轮训去查询redis中是否有这个唯一值的数据,如果有就获取APP用户信息信息,没有就五秒一次轮训,登录后就不在做轮训(从二维码弹出之后开始做轮训,关闭二维码后停止轮训)
- 这里的唯一值是可以自己定义的,我用的是截取了几位的时间戳
实现步骤
根据上面的实现思路,我们用java写一个获取带参数的临时二维码接口。以下是主要代码:
// 临时二维码 private final static String QR_SCENE = "QR_SCENE"; // 永久二维码 private final static String QR_LIMIT_SCENE = "QR_LIMIT_SCENE"; // 永久二维码(字符串) private final static String QR_LIMIT_STR_SCENE = "QR_LIMIT_STR_SCENE"; // 创建二维码 private String create_ticket_path = "https://api.weixin.qq.com/cgi-bin/qrcode/create"; // 通过ticket换取二维码 private String showqrcode_path = "https://mp.weixin.qq.com/cgi-bin/showqrcode"; @RequestMapping("getQrcode") public @ResponseBody String getQrcode(@RequestParam(value = "sceneId")int sceneId) throws Exception{ String ticket = createTempTicket(tokenService.getToken(),"2592000",sceneId); LOGGER.info("get wechat qrcode ==> start"); LOGGER.info("sceneId :"+sceneId); LOGGER.info("ticket :"+ticket); LOGGER.info("get wechat qrcode ==> end"); return ticket; } /** * 创建临时带参数二维码 * @param accessToken * @expireSeconds 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。 * @param sceneId 场景Id * @return // 业余草:www.xttblog.com */ public String createTempTicket(String accessToken, String expireSeconds, int sceneId) { TreeMap<String,String> params = new TreeMap<String,String>(); params.put("access_token", accessToken); Map<String,Integer> intMap = new HashMap<String,Integer>(); intMap.put("scene_id",sceneId); Map<String,Map<String,Integer>> mapMap = new HashMap<String,Map<String,Integer>>(); mapMap.put("scene", intMap); Map<String,Object> paramsMap = new HashMap<String,Object>(); paramsMap.put("expire_seconds", expireSeconds); paramsMap.put("action_name", QR_SCENE); paramsMap.put("action_info", mapMap); String data = new Gson().toJson(paramsMap); String tse = HttpRequestUtil.HttpsDefaultExecute(HttpRequestUtil.POST_METHOD,create_ticket_path,params,data); JSONObject jsonObject = JSONObject.fromObject(tse); LOGGER.info("ticket :"+jsonObject.getString("ticket")); return showqrcode_path+"?ticket="+jsonObject.getString("ticket"); }
二维码的生成,相信大家都会,我这里直接调用微信的接口是为了整合微信做其他功能。
配合SpringMVC做一个接口:
@RequestMapping("getQrcode") public @ResponseBody Hashtable getQrcode(int sceneId){ System.out.println(sceneId); Hashtable param = new Hashtable(); param.put("sceneId", sceneId); String qrcodePath = HttpUtil.postRequest(Constant.getValue("get_qrcode"), param); System.out.println(" qrcodePath ==> "+qrcodePath); param.put("path", qrcodePath); return param;// 业余草:www.xttblog.com }
网站端:登陆页面中做轮训,每隔几秒查询一次redis,如果有用户信息就登陆。
var timestamp = new Date().getTime() + ""; var str = timestamp.substring(8, timestamp.length); window.setInterval(function() { getUser(cont); }, 10000); function getUser() { $.ajax({ type : 'get', data : { sceneId : str }, dataType : 'json', url : "getUser.do", success : function(data) { if (data.msg == "success") { location.reload(); } }, error : function(data) { if (data.msg == "success") { location.reload(); } }// 业余草:www.xttblog.com }); }
上面使用的轮询效率不高,我们可以借助websocket来实现(实际应用中自己判断浏览器是否支持WebSocket,支持就优先WebSocket,不支持就是用轮询)。
使用websocket建立一个连接很容易,代码如下:
var timestamp = new Date().getTime() + ""; timestamp = timestamp.substring(0, timestamp.length-3); var websocket = null; //判断当前浏览器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://www.xttblog.com/wx/websocket/"+timestamp); } else { alert('当前浏览器 Not support websocket'); } //连接发生错误的回调方法 websocket.onerror = function () { console.log("WebSocket连接发生错误"); }; //连接成功建立的回调方法 websocket.onopen = function () { console.log("WebSocket连接成功"); } //接收到消息的回调方法 websocket.onmessage = function (event) { jQuery("#username").html(event.data); jQuery("#singup").css("display", "none"); jQuery("#user").show(); jQuery("#singout").show(); layer.closeAll(); } //连接关闭的回调方法 websocket.onclose = function () { console.log("WebSocket连接关闭"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接, //防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { closeWebSocket(); } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); }
服务器端socket的建立,在建立socket链接的时候,将每个页面传过来的唯一标识{sceneId}与会话信息session进行绑定,可以使用map实现,这里用的ConcurrentMap。
/** * @ServerEndpoint 注解是一个类层次的注解, * 它的功能主要是将目前的类定义成一个websocket服务器端, * 注解的值将被用于监听用户连接的终端访问URL地址, * 客户端可以通过这个URL来连接到WebSocket服务器端 */ @Component @ServerEndpoint("/websocket/{sceneId}") public class WebSocketController { //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 //若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 private static ConcurrentMap<String,WebSocketController> webSocketMap = new ConcurrentHashMap<String,WebSocketController>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; /** * 连接建立成功调用的方法 * @param session 可选的参数。session为与某个客户端的连接会话, * 需要通过它来给客户端发送数据 */ @OnOpen public void onOpen(@PathParam("sceneId") String sceneId,Session session){ this.session = session; webSocketMap.put(sceneId, this); //在线数加1 System.out.println("唯一key为:" + sceneId); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(@PathParam("sceneId") String sceneId){ webSocketMap.remove(sceneId);//从map中删除 } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 可选的参数 */ @OnMessage public void onMessage(String message,Session session) { System.out.println("来自客户端的消息:" + message); JSONObject jsonobject = JSONObject.fromObject(message); Hashtable params= (Hashtable)JSONObject.toBean(jsonobject,Hashtable.class); //群发消息 WebSocketController webSocketController = webSocketMap.get(params.get("equipmentType")); try { webSocketController.sendMessage((String)params.get("nickname")); } catch (IOException e) { e.printStackTrace(); } } /** * 发生错误时调用 * @param session * @param error */ @OnError public void onError(Session session, Throwable error){ System.out.println("发生错误"); error.printStackTrace(); } /** * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。 * @param message * @throws IOException */ public void sendMessage(String message) throws IOException{ this.session.getBasicRemote().sendText(message); }
以上就是整个扫码登录功能的核心代码。有兴趣的可以结合我的实现思路和部分代码自己实现一个。
最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » websocket实现APP扫描二维码,自动登陆网站(扫码登录)