Build QR‑Code Login with WebSocket in Spring Boot
This guide walks through designing a User_Token table, defining roles and APIs, implementing QR‑code generation and validation endpoints, and wiring a Spring Boot WebSocket server with front‑end AJAX to achieve secure, real‑time QR‑code login for Android, web and PC clients.
Overview
The article presents a complete solution for implementing QR‑code based login using WebSocket in a Spring Boot application. It covers database design, role analysis, API definitions, step‑by‑step workflow, front‑end AJAX handling, and back‑end WebSocket configuration.
1. Database Table Design
User_Token table
Fields:
uuid – unique identifier for each QR code session
userId – the ID of the user who logs in
loginTime – timestamp of successful login
createTime – record creation time, used to check expiration
state – 0 for valid, 1 for expired
2. Roles Involved
Android or WeChat web client – scans the QR code
PC client – displays the QR code and receives login confirmation
Server – generates QR codes, validates tokens, and pushes login results via WebSocket
3. Required APIs
Generate QR code – creates a UUID, stores it in User_Token, and returns the QR image
Confirm identity – validates the token, checks expiration, updates the login record, and notifies the PC client
4. Workflow Steps
PC calls the generate‑QR API, receives a UUID in the HTTP header, and displays the QR image.
The mobile/web client scans the QR code, extracts the UUID, and shows a login confirmation page.
After the user confirms, the client calls the confirm‑identity API with the UUID and user information.
The server validates the token, updates the database, and pushes a success message to the PC client via WebSocket; the connection is then closed.
5. Front‑End Implementation
AJAX request to obtain the QR image and UUID:
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 img = document.createElement("img");
img.src = window.URL.createObjectURL(this.response);
document.getElementById("qrImgDiv").appendChild(img);
initWebSocket(uuid);
}
};
xmlhttp.send();WebSocket initialization and message handling:
var ws = new WebSocket(wsPath + uuid);
ws.onmessage = function (msg) {
var data = JSON.parse(msg.data);
if (data.code == 200) {
alert("Login successful!");
sessionStorage.uuid = uuid;
sessionStorage.userId = data.userId;
sessionStorage.projId = data.projId;
location.href = "pages/upload.html";
} else {
ws.close();
initQrImg(); // refresh QR if expired
}
};
ws.onclose = function () { console.log("Socket closed"); };
ws.onerror = function () { alert("Socket error"); };6. Back‑End WebSocket Setup (Spring Boot)
Add the WebSocket starter dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>Register the endpoint exporter bean:
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}WebSocket server implementation (simplified):
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
private static int onlineCount = 0;
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
private Session session;
private String sid = "";
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
this.sid = sid;
webSocketSet.add(this);
addOnlineCount();
log.info("New connection: " + sid + ", online: " + getOnlineCount());
}
@OnClose
public void onClose() {
webSocketSet.remove(this);
subOnlineCount();
log.info("Connection closed, online: " + getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) {
for (WebSocketServer client : webSocketSet) {
try { client.sendMessage(message); } catch (IOException e) { e.printStackTrace(); }
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("WebSocket error", error);
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
public static void sendInfo(String message, String sid) throws IOException {
for (WebSocketServer client : webSocketSet) {
if (sid == null || client.sid.equals(sid)) {
client.sendMessage(message);
}
}
}
// synchronized online count helpers omitted for brevity
}7. QR‑Code Generation Endpoint
@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(); }
}8. Confirm‑Identity Endpoint and Service Logic
@RequestMapping(value = "/bindUserIdAndToken", method = RequestMethod.GET)
@ResponseBody
public Object bindUserIdAndToken(@RequestParam("token") String token,
@RequestParam("userId") Integer userId,
@RequestParam(value = "projId", required = false) Integer projId) {
try {
return new SuccessTip(userService.bindUserIdAndToken(userId, token, projId));
} catch (Exception e) {
e.printStackTrace();
return new ErrorTip(500, e.getMessage());
}
}Service method (simplified):
public String bindUserIdAndToken(Integer userId, String token, Integer projId) throws Exception {
QrLoginToken tokenRec = qrLoginTokenMapper.selectOne(new QrLoginToken().setToken(token));
if (tokenRec == null) throw new Exception("Invalid request!");
Date expire = new Date(tokenRec.getCreateTime().getTime() + 1000 * 60 * Constant.LOGIN_VALIDATION_TIME);
if (new Date().after(expire)) {
JSONObject resp = new JSONObject();
resp.put("code", 500);
resp.put("msg", "QR code expired!");
WebSocketServer.sendInfo(resp.toJSONString(), token);
throw new Exception("QR code expired!");
}
tokenRec.setLoginTime(new Date());
tokenRec.setUserId(userId);
qrLoginTokenMapper.updateById(tokenRec);
JSONObject resp = new JSONObject();
resp.put("code", 200);
resp.put("msg", "ok");
resp.put("userId", userId);
if (ToolUtil.isNotEmpty(projId)) resp.put("projId", projId);
WebSocketServer.sendInfo(resp.toJSONString(), token);
return null;
}The service validates the token, checks expiration, updates the login record, and pushes a JSON message to the PC client via the WebSocket server.
9. End‑to‑End Flow Recap
PC requests /getLoginQr, receives QR image and UUID in the response header.
Mobile/web client scans the QR, extracts the UUID, and calls /bindUserIdAndToken with the token and user ID.
The server validates the token, ensures the QR is not expired, updates the database, and sends a success message through WebSocket.
The PC client receives the WebSocket message, stores session data, and redirects to the protected page.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow 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.
