Implementing QR‑Code Login with WebSocket in Spring Boot
This article explains how to design a QR‑code login flow, create the necessary database table, define the required APIs, and integrate front‑end JavaScript with a Spring Boot back‑end using WebSocket to notify the client of successful authentication.
1. Database Table Design
A User_Token table records who scanned the QR code and who logged in, with fields: uuid (unique identifier), userId , loginTime , createTime (for expiration), and state (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 the WebSocket connection.
3. Required APIs
Generate QR code : returns a QR image containing a UUID.
Confirm identity : validates the UUID, checks expiration, and returns login status.
4. Process Steps
PC opens the page, calls the generate‑QR API, and binds the returned UUID to a WebSocket session.
The web client (WeChat) scans the QR code and obtains the UUID.
The web client displays a confirmation page; when the user confirms, it calls the confirm‑identity API.
After successful validation, the server pushes a login‑success message through WebSocket, then closes the connection.
5. Front‑end Implementation
JavaScript uses XMLHttpRequest to request the QR image as a binary blob , extracts the uuid from the response header, displays the image, and then opens a WebSocket using the UUID as the session identifier.
@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();
}
} $(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.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("Your browser does not support WebSocket");
} else {
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"); };
}
}6. Spring Boot WebSocket Configuration
Add the spring-boot-starter-websocket dependency to pom.xml and expose a ServerEndpointExporter bean.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency> @Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}Implement the WebSocket server endpoint that tracks sessions, broadcasts messages, and provides a static sendInfo method for pushing login results to the specific client identified by the UUID.
package com.stylefeng.guns.rest.modular.inve.websocket;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
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();
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){ error.printStackTrace(); }
public void sendMessage(String message) throws IOException{ 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; }
}
}
}
// synchronized online‑count helpers omitted for brevity
}7. Confirm‑Identity Controller & Service
The controller receives token , userId , and optional projId , delegates to the service, and returns a JSON result. The service validates the token, checks expiration, updates the login record, and pushes the result through WebSocketServer.sendInfo .
@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());
}
} @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 expire = new Date(qrLoginToken.getCreateTime().getTime() + 1000*60*Constant.LOGIN_VALIDATION_TIME);
if(new Date().after(expire)){
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;
}Overall, the article walks through the complete QR‑code login solution: database schema, API design, front‑end JavaScript handling, and Spring Boot WebSocket integration, enabling real‑time login notification without page refresh.
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.