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.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Implement QR Code Login with WebSocket in Spring Boot: Step-by-Step Guide

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootWebSocketQR Code Login
Su San Talks Tech
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.