Implement QR Code Login with WebSocket Using Spring Boot
This article explains how to build a QR‑code based login system by designing a token table, defining roles, creating two REST APIs (generate QR code and confirm identity), and integrating front‑end JavaScript with a Spring Boot WebSocket server, complete with full code examples and step‑by‑step instructions.
Overview
The tutorial demonstrates a complete QR‑code login solution where a user scans a QR code on a PC using a mobile or WeChat web client, and the server authenticates the login via WebSocket communication.
1. Database Design
A User_Token table records each QR login attempt with fields:
uuid – unique identifier
userId – the user who logs in
loginTime – time of login
createTime – record creation time (used for expiration)
state – 0 for valid, 1 for expired
2. Roles Involved
Android/WeChat web client – scans the QR code
PC client – displays the QR code and waits for login
Backend server – provides APIs and WebSocket notifications
3. Required APIs
@RequestMapping(value="/getLoginQr", method=RequestMethod.GET) – generates a QR image containing a UUID and stores it in the token table.
@RequestMapping(value="/bindUserIdAndToken", method=RequestMethod.GET) – validates the token, checks expiration, binds the user ID, and pushes the result via WebSocket.
4. Process Flow
PC calls the QR‑generation API, receives an image stream, extracts the uuid from the response header, and displays the QR code.
Mobile client scans the QR, obtains the uuid , and calls the confirmation API with token (the UUID) and userId .
The server validates the token, updates its status, and sends a WebSocket message to the PC client.
PC receives the message, stores session data, and redirects to the target page.
5. Front‑end Implementation
JavaScript uses XMLHttpRequest to request the QR image, reads the uuid from the header, creates an <img> element, and then opens a WebSocket connection using the UUID as the session identifier.
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.src = window.URL.createObjectURL(blob);
document.getElementById("qrImgDiv").appendChild(img);
initWebSocket();
}
};
xmlhttp.send();
}6. WebSocket Server (Spring Boot)
The server is enabled by adding spring-boot-starter-websocket to pom.xml and defining a ServerEndpointExporter bean.
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}The WebSocketServer class manages connections, tracks online count, and provides methods to send messages to specific sessions or broadcast to all.
@ServerEndpoint("/websocket/{sid}")
public class WebSocketServer {
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;
this.sid = sid;
webSocketSet.add(this);
addOnlineCount();
}
@OnMessage
public void onMessage(String message, Session session){
for(WebSocketServer client : webSocketSet){
client.sendMessage(message);
}
}
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
}
// additional onClose, onError, and utility methods omitted for brevity
}7. Spring Boot Configuration
In pom.xml add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>Define the bean as shown above.
8. Controller and Service Logic
The controller exposes the two endpoints; the service validates the token, checks expiration, updates the record, and pushes the result via 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());
}
} public String bindUserIdAndToken(Integer userId, String token, Integer projId) throws Exception{
QrLoginToken tokenEntity = qrLoginTokenMapper.selectOne(new QrLoginToken().setToken(token));
if(tokenEntity == null){
throw new Exception("Invalid request!");
}
// expiration check
Date expire = new Date(tokenEntity.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!");
}
tokenEntity.setLoginTime(new Date());
tokenEntity.setUserId(userId);
qrLoginTokenMapper.updateById(tokenEntity);
JSONObject json = new JSONObject();
json.put("code",200);
json.put("msg","ok");
json.put("userId",userId);
if(projId != null) json.put("projId",projId);
WebSocketServer.sendInfo(json.toJSONString(), token);
return null;
}9. End‑to‑End Flow
1) PC requests /getLoginQr → receives QR image with UUID. 2) Mobile scans QR, extracts UUID, and calls /bindUserIdAndToken with user ID. 3) Server validates token, updates DB, and pushes a WebSocket message to the PC. 4) PC receives the message, stores session data, and redirects to the authenticated page.
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.