我了解网站扫描二维码并实现自动登陆功能是从微信哪里看到的,该功能体验还不错,减少密码被盗的风险。那么一个普通的网站该如何实现此功能呢?本文将借助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);
}
以上就是整个扫码登录功能的核心代码。有兴趣的可以结合我的实现思路和部分代码自己实现一个。

: » websocket实现APP扫描二维码,自动登陆网站(扫码登录)
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/251697.html