Implement QR Code Login with WebSocket in Spring Boot: Step-by-Step Guide
Learn how to build a QR‑code login system using Spring Boot and WebSocket, covering database design, role analysis, API creation, front‑end integration, and full Java code examples for generating QR images, handling socket communication, and validating tokens with expiration logic.
Recently a project required implementing QR‑code login using WebSocket, so this article shares a complete technical solution.
1. Database Table
User_Token table
uuid: unique identifier
userId: who logged in
loginTime: login timestamp
createTime: creation time (used to check expiration)
state: QR code status (0 = valid, 1 = invalid)
2. Roles Involved
Android or WeChat web client – scans the QR code
PC client – receives the scan and logs in
Server – provides APIs and manages WebSocket communication
3. Required APIs
Generate QR code API – creates a QR image containing a UUID
Confirm identity API – validates the token, checks expiration, and returns login result
4. Process Steps
PC calls the generate‑QR API, receives a UUID in the response header, and displays the QR image.
The mobile/web client scans the QR code, extracts the UUID.
The client calls the confirm‑identity API with the UUID and userId.
If validation succeeds, the server pushes a success message to the PC via WebSocket and closes the connection.
5. Code Implementation
Generate QR code 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);
QrCodeUtil.generate(uuid, 300, 300, "jpg", response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}Front‑end AJAX to fetch QR image and UUID:
$(document).ready(function(){
initQrImg();
});
function initQrImg(){
$("#qrImgDiv").empty();
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", getQrPath, true);
xmlhttp.responseType = "blob";
xmlhttp.onload = function(){
uuid = this.getResponseHeader("uuid");
if (this.status == 200) {
var blob = this.response;
var img = document.createElement("img");
img.src = window.URL.createObjectURL(blob);
document.getElementById("qrImgDiv").appendChild(img);
initWebSocket();
}
};
xmlhttp.send();
}
var wsPath = "ws://localhost:8085/websocket/";
function initWebSocket(){
if (typeof(WebSocket) == "undefined") {
console.log("Your browser does not support WebSocket");
return;
}
var socket = new WebSocket(wsPath + uuid);
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"); };
}Spring Boot WebSocket configuration:
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}WebSocket server implementation:
@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();
this.sid = sid;
}
@OnClose
public void onClose(){
webSocketSet.remove(this);
subOnlineCount();
}
@OnMessage
public void onMessage(String message, Session session){
for (WebSocketServer item : webSocketSet) {
try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); }
}
}
@OnError
public void onError(Session session, Throwable error){
log.error("Error occurred");
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)) {
item.sendMessage(message);
}
}
}
public static synchronized int getOnlineCount(){ return onlineCount; }
public static synchronized void addOnlineCount(){ onlineCount++; }
public static synchronized void subOnlineCount(){ onlineCount--; }
}Confirm‑identity controller:
@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 expiration:
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);
Date now = new Date();
if (now.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);
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);
return null;
}The overall flow is: PC requests a QR image, receives a UUID, displays it; the mobile client scans the QR, sends the UUID and userId to the confirm‑identity API; the server validates the token and expiration, updates the login record, and pushes a success message to the PC via WebSocket, completing the login.
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.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
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.
