Implementing QR‑Code Login with WebSocket in Spring Boot
This article explains how to design a QR‑code login flow using a MySQL table, defines the involved roles, outlines required REST and WebSocket interfaces, and provides complete Spring Boot backend and JavaScript frontend code examples for generating QR codes, validating tokens, and notifying the client in real time.
First, a User_Token table is created to store the UUID, user ID, login time, creation time, and state (valid/invalid) of each QR‑code login attempt.
The login process involves three roles: the Android/WeChat web client that scans the QR code, the PC client that receives the login request, and the backend server that coordinates the flow.
Two REST endpoints are required:
Generate QR code endpoint – returns an image containing a UUID and stores the UUID in the database.
Confirm identity endpoint – validates the token, checks expiration, updates the record, and pushes a success message.
The workflow consists of four steps:
PC client calls the QR‑code generation API and opens a WebSocket connection using the UUID as the session identifier.
The mobile/web client scans the QR code and extracts the UUID.
The mobile/web client calls the confirm‑identity API with the UUID and user ID.
Upon successful validation, the server pushes a login‑success message through the WebSocket, after which the PC client redirects to the target page.
Backend code snippets :
@RequestMapping(value = "/getLoginQr", method = RequestMethod.GET)
public void createCodeImg(HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
try {
String uuid = userService.createQrImg();
response.setHeader("uuid", uuid);
QrCodeUtil.generate(uuid, 300, 300, "jpg", response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
} @RequestMapping(value = "/bindUserIdAndToken", method = RequestMethod.GET)
@ResponseBody
public Object bindUserIdAndToken(@RequestParam("token") String token,
@RequestParam("userId") Integer userId,
@RequestParam(required = false, value = "projId") Integer projId) {
try {
return new SuccessTip(userService.bindUserIdAndToken(userId, token, projId));
} catch (Exception e) {
e.printStackTrace();
return new ErrorTip(500, e.getMessage());
}
} package com.stylefeng.guns.rest.modular.inve.websocket;
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
private static Log log = LogFactory.get(WebSocketServer.class);
private static int onlineCount = 0;
private static CopyOnWriteArraySet
webSocketSet = new CopyOnWriteArraySet<>();
private Session session;
private String sid = "";
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this);
addOnlineCount();
log.info("New connection: " + sid + ", online: " + getOnlineCount());
this.sid = sid;
}
@OnClose
public void onClose() {
webSocketSet.remove(this);
subOnlineCount();
log.info("Connection closed, online: " + getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) {
log.info("Message from " + sid + ": " + message);
for (WebSocketServer item : webSocketSet) {
try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); }
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("WebSocket error");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
for (WebSocketServer item : webSocketSet) {
if (sid == null || item.sid.equals(sid)) {
try { item.sendMessage(message); } catch (IOException e) { continue; }
}
}
}
public static synchronized int getOnlineCount() { return onlineCount; }
public static synchronized void addOnlineCount() { onlineCount++; }
public static synchronized void subOnlineCount() { onlineCount--; }
}Frontend code snippets :
$(document).ready(function(){ initQrImg(); });
function initQrImg(){
$("#qrImgDiv").empty();
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", getQrPath, true);
xmlhttp.responseType = "blob";
xmlhttp.onload = function(){
var uuid = this.getResponseHeader("uuid");
if (this.status == 200) {
var blob = this.response;
var img = document.createElement("img");
img.className = 'qrCodeBox-img';
img.onload = function(){ window.URL.revokeObjectURL(img.src); };
img.src = window.URL.createObjectURL(blob);
document.getElementById("qrImgDiv").appendChild(img);
initWebSocket();
}
};
xmlhttp.send();
}
var path = "://localhost:8085";
var getQrPath = "http" + path + "/user/getLoginQr";
var wsPath = "ws" + path + "/websocket/";
function initWebSocket(){
if (typeof(WebSocket) == "undefined") {
console.log("Browser does not support WebSocket");
} else {
console.log("Browser supports WebSocket");
var wsPathStr = wsPath + uuid;
socket = new WebSocket(wsPathStr);
socket.onopen = function(){ console.log("Socket opened"); };
socket.onmessage = function(msg){
var data = JSON.parse(msg.data);
if (data.code == 200){
alert("Login successful!");
window.sessionStorage.uuid = uuid;
window.sessionStorage.userId = data.userId;
window.sessionStorage.projId = data.projId;
window.location.href = "pages/upload.html";
} else {
socket.close();
initQrImg();
}
};
socket.onclose = function(){ console.log("Socket closed"); };
socket.onerror = function(){ alert("Socket error"); };
}
}Finally, the service layer validates the token, checks expiration, updates the login record, and uses WebSocketServer.sendInfo to push the result to the PC client, completing the QR‑code login flow.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.