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<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){ 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.
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.
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.
