How to Build QR Code Login with WebSocket in Spring Boot
This tutorial walks through designing a QR‑code login flow, defining a token table, outlining client and server roles, implementing two REST endpoints, configuring Spring Boot WebSocket support, and handling real‑time login notifications with Java and JavaScript code examples.
1. Database Table
We need a User_Token table to record who scanned the QR code and who logged in. The fields are:
uuid – unique identifier
userId – the logged‑in user
loginTime – time of login
createTime – record creation time (used to check expiration)
state – QR code status (0 = valid, 1 = invalid)
2. Roles
The login process involves three roles:
Android or WeChat web client – scans the QR code
PC client – receives the scan and performs the login
Server – provides APIs and coordinates the flow
3. Required APIs
Two REST endpoints are needed:
Generate QR code – creates a QR image that contains a UUID.
Confirm identity – validates the token, checks expiration, and completes the login.
4. Process Steps
PC calls the generate‑QR API, receives a UUID in the response header, and displays the QR image.
The web client scans the QR, extracts the UUID, and calls the confirm‑identity API.
If validation succeeds, the server pushes a success message to the PC via WebSocket.
The PC receives the message, stores session data, and redirects to the target page.
5. Backend Code – Generate QR
<code>//获取登录二维码、放入Token
@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 {
// Generate a UUID and store it in the database
String uuid = userService.createQrImg();
response.setHeader("uuid", uuid);
// Use Hutool's QrCodeUtil to generate the image
QrCodeUtil.generate(uuid, 300, 300, "jpg", response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
</code>6. Frontend Code – Load QR and Open WebSocket
<code>$(document).ready(function(){
initQrImg();
});
function initQrImg(){
$("#qrImgDiv").empty();
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", getQrPath, true);
xmlhttp.responseType = "blob";
xmlhttp.onload = function(){
console.log(this);
uuid = this.getResponseHeader("uuid");
if(this.status == 200) {
var blob = this.response;
var img = document.createElement("img");
img.className = 'qrCodeBox-img';
img.onload = function(e) { 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("Your browser does not support WebSocket");
} else {
console.log("Your browser supports WebSocket");
var wsPathStr = wsPath + uuid;
socket = new WebSocket(wsPathStr);
socket.onopen = function() { console.log("Socket opened"); };
socket.onmessage = function(msg) {
console.log(msg.data);
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"); };
}
}
</code>7. Spring Boot WebSocket Configuration
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</code> <code>@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
</code> <code>@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
private static Log log = LogFactory.get(WebSocketServer.class);
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;
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("Received 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--; }
}
</code>8. Confirm‑Identity API
<code>@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());
}
}
</code> <code>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 i = 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 (i > 0) { return null; } else { throw new Exception("Server error!"); }
}
</code>The overall logic is: the PC obtains a UUID via the QR‑generation API, the mobile/web client scans the QR and calls the confirm‑identity API with the UUID and user ID, the server validates the token and expiration, updates the database, and pushes a success message to the PC through WebSocket, completing the login flow.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.