websocket实现APP扫描二维码,自动登陆网站(扫码登录)

JAVA herman 6378浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

我了解网站扫描二维码并实现自动登陆功能是从微信哪里看到的,该功能体验还不错,减少密码被盗的风险。那么一个普通的网站该如何实现此功能呢?本文将借助websocket+java实现这一功能。

实现思路

  1. 后台系统,提供生成带参数的二维码的接口,这个参数就是唯一值(场景值)
  2. 访问到登录网站页面 时,生成二维码
  3. 用户拿APP扫码后,APP直接将场景值中的唯一值和用户信息反馈给后台系统
  4. 后台系统将用户访问的唯一值信息记录到redis,key就是唯一值(场景值)
  5. 网站端做轮训去查询redis中是否有这个唯一值的数据,如果有就获取APP用户信息信息,没有就五秒一次轮训,登录后就不在做轮训(从二维码弹出之后开始做轮训,关闭二维码后停止轮训)
  6. 这里的唯一值是可以自己定义的,我用的是截取了几位的时间戳

实现步骤

根据上面的实现思路,我们用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)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » websocket实现APP扫描二维码,自动登陆网站(扫码登录)