Implementing QR Code Login with WebSocket in Spring Boot
This article explains how to build a QR‑code based login system using Spring Boot, WebSocket, and JavaScript, covering database design, role analysis, API definitions, step‑by‑step implementation, and complete code examples for both backend and frontend components.
In a new Java project the author demonstrates a QR‑code login flow that replaces traditional Ajax polling with a WebSocket‑based solution.
A User_Token table is defined to store uuid , userId , loginTime , createTime , and state (0 = valid, 1 = invalid).
The system 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 orchestrates the process.
Two REST APIs are required: one to generate the QR code (embedding a UUID) and another to confirm the identity and check expiration.
The implementation steps are:
PC client calls the QR‑generation API and opens a WebSocket connection using the returned UUID.
The mobile/web client scans the QR code, extracts the UUID, and displays a confirmation page.
After user confirmation, the mobile/web client calls the identity‑confirmation API.
The server validates the token, updates the database, and pushes a success message to the PC client via WebSocket, then closes the connection.
Below are the essential code snippets.
// QR code generation endpoint (Spring MVC)
@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);
// Using Hutool's QrCodeUtil to generate the image
QrCodeUtil.generate(uuid, 300, 300, "jpg", response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
} // Front‑end JavaScript to fetch QR image and open WebSocket
$(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(uuid);
}
};
xmlhttp.send();
}
function initWebSocket(uuid){
if (typeof(WebSocket) == "undefined") { console.log("Browser does not support WebSocket"); return; }
var ws = new WebSocket("ws://localhost:8085/websocket/" + uuid);
ws.onopen = function(){ console.log("Socket opened"); };
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();
}
};
ws.onclose = function(){ console.log("Socket closed"); };
ws.onerror = function(){ alert("Socket error"); };
} // WebSocket server implementation (Spring Boot)
@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 client : webSocketSet) {
try { client.sendMessage(message); } catch (IOException e) { e.printStackTrace(); }
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("Error: ");
error.printStackTrace();
}
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 counters
public static synchronized int getOnlineCount() { return onlineCount; }
public static synchronized void addOnlineCount() { onlineCount++; }
public static synchronized void subOnlineCount() { onlineCount--; }
} // Controller for confirming identity
@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());
}
} // Service method handling token validation and login
@Override
public String bindUserIdAndToken(Integer userId, String token, Integer projId) throws Exception {
QrLoginToken qrLoginToken = new QrLoginToken();
qrLoginToken.setToken(token);
qrLoginToken = qrLoginTokenMapper.selectOne(qrLoginToken);
if (qrLoginToken == null) {
throw new Exception("Invalid request!");
}
Date expireTime = new Date(qrLoginToken.getCreateTime().getTime() + 1000 * 60 * Constant.LOGIN_VALIDATION_TIME);
if (new Date().after(expireTime)) {
JSONObject json = new JSONObject();
json.put("code", 500);
json.put("msg", "QR code expired!");
WebSocketServer.sendInfo(json.toJSONString(), token);
throw new Exception("QR code expired!");
}
qrLoginToken.setLoginTime(new Date());
qrLoginToken.setUserId(userId);
int updated = qrLoginTokenMapper.updateById(qrLoginToken);
JSONObject json = new JSONObject();
json.put("code", 200);
json.put("msg", "ok");
json.put("userId", userId);
if (ToolUtil.isNotEmpty(projId)) { json.put("projId", projId); }
WebSocketServer.sendInfo(json.toJSONString(), token);
if (updated > 0) return null;
else throw new Exception("Server error!");
}The overall flow validates the token, checks expiration, updates the login record, and pushes a success message to the PC client via WebSocket, after which the front‑end proceeds with its business logic.
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.