Build QR‑Code Login with WebSocket in Spring Boot

This guide walks through designing a User_Token table, defining roles and APIs, implementing QR‑code generation and validation endpoints, and wiring a Spring Boot WebSocket server with front‑end AJAX to achieve secure, real‑time QR‑code login for Android, web and PC clients.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Build QR‑Code Login with WebSocket in Spring Boot

Overview

The article presents a complete solution for implementing QR‑code based login using WebSocket in a Spring Boot application. It covers database design, role analysis, API definitions, step‑by‑step workflow, front‑end AJAX handling, and back‑end WebSocket configuration.

1. Database Table Design

User_Token table

Fields:

uuid – unique identifier for each QR code session

userId – the ID of the user who logs in

loginTime – timestamp of successful login

createTime – record creation time, used to check expiration

state – 0 for valid, 1 for expired

2. Roles Involved

Android or WeChat web client – scans the QR code

PC client – displays the QR code and receives login confirmation

Server – generates QR codes, validates tokens, and pushes login results via WebSocket

3. Required APIs

Generate QR code – creates a UUID, stores it in User_Token, and returns the QR image

Confirm identity – validates the token, checks expiration, updates the login record, and notifies the PC client

4. Workflow Steps

PC calls the generate‑QR API, receives a UUID in the HTTP header, and displays the QR image.

The mobile/web client scans the QR code, extracts the UUID, and shows a login confirmation page.

After the user confirms, the client calls the confirm‑identity API with the UUID and user information.

The server validates the token, updates the database, and pushes a success message to the PC client via WebSocket; the connection is then closed.

5. Front‑End Implementation

AJAX request to obtain the QR image and UUID:

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 img = document.createElement("img");
        img.src = window.URL.createObjectURL(this.response);
        document.getElementById("qrImgDiv").appendChild(img);
        initWebSocket(uuid);
    }
};
xmlhttp.send();

WebSocket initialization and message handling:

var ws = new WebSocket(wsPath + uuid);
ws.onmessage = function (msg) {
    var data = JSON.parse(msg.data);
    if (data.code == 200) {
        alert("Login successful!");
        sessionStorage.uuid = uuid;
        sessionStorage.userId = data.userId;
        sessionStorage.projId = data.projId;
        location.href = "pages/upload.html";
    } else {
        ws.close();
        initQrImg(); // refresh QR if expired
    }
};
ws.onclose = function () { console.log("Socket closed"); };
ws.onerror = function () { alert("Socket error"); };

6. Back‑End WebSocket Setup (Spring Boot)

Add the WebSocket starter dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Register the endpoint exporter bean:

@Bean
public ServerEndpointExporter serverEndpointExporter() {
    return new ServerEndpointExporter();
}

WebSocket server implementation (simplified):

@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;
        this.sid = sid;
        webSocketSet.add(this);
        addOnlineCount();
        log.info("New connection: " + sid + ", online: " + getOnlineCount());
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        subOnlineCount();
        log.info("Connection closed, online: " + getOnlineCount());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        for (WebSocketServer client : webSocketSet) {
            try { client.sendMessage(message); } catch (IOException e) { e.printStackTrace(); }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket error", error);
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    public static void sendInfo(String message, String sid) throws IOException {
        for (WebSocketServer client : webSocketSet) {
            if (sid == null || client.sid.equals(sid)) {
                client.sendMessage(message);
            }
        }
    }

    // synchronized online count helpers omitted for brevity
}

7. QR‑Code Generation Endpoint

@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(); }
}

8. Confirm‑Identity Endpoint and Service Logic

@RequestMapping(value = "/bindUserIdAndToken", method = RequestMethod.GET)
@ResponseBody
public Object bindUserIdAndToken(@RequestParam("token") String token,
                                 @RequestParam("userId") Integer userId,
                                 @RequestParam(value = "projId", required = false) Integer projId) {
    try {
        return new SuccessTip(userService.bindUserIdAndToken(userId, token, projId));
    } catch (Exception e) {
        e.printStackTrace();
        return new ErrorTip(500, e.getMessage());
    }
}

Service method (simplified):

public String bindUserIdAndToken(Integer userId, String token, Integer projId) throws Exception {
    QrLoginToken tokenRec = qrLoginTokenMapper.selectOne(new QrLoginToken().setToken(token));
    if (tokenRec == null) throw new Exception("Invalid request!");
    Date expire = new Date(tokenRec.getCreateTime().getTime() + 1000 * 60 * Constant.LOGIN_VALIDATION_TIME);
    if (new Date().after(expire)) {
        JSONObject resp = new JSONObject();
        resp.put("code", 500);
        resp.put("msg", "QR code expired!");
        WebSocketServer.sendInfo(resp.toJSONString(), token);
        throw new Exception("QR code expired!");
    }
    tokenRec.setLoginTime(new Date());
    tokenRec.setUserId(userId);
    qrLoginTokenMapper.updateById(tokenRec);
    JSONObject resp = new JSONObject();
    resp.put("code", 200);
    resp.put("msg", "ok");
    resp.put("userId", userId);
    if (ToolUtil.isNotEmpty(projId)) resp.put("projId", projId);
    WebSocketServer.sendInfo(resp.toJSONString(), token);
    return null;
}

The service validates the token, checks expiration, updates the login record, and pushes a JSON message to the PC client via the WebSocket server.

9. End‑to‑End Flow Recap

PC requests /getLoginQr, receives QR image and UUID in the response header.

Mobile/web client scans the QR, extracts the UUID, and calls /bindUserIdAndToken with the token and user ID.

The server validates the token, ensures the QR is not expired, updates the database, and sends a success message through WebSocket.

The PC client receives the WebSocket message, stores session data, and redirects to the protected page.

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.

JavaBackend DevelopmentSpring BootWebSocketAuthenticationQR Code Login
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.