[]
        
(Showing Draft Content)

튜토리얼: 인증 추가

이 튜토리얼은 인증 및 권한 관리를 구현하기 위해 서버와 클라이언트 코드를 수정하는 과정을 개발자에게 안내합니다. 다음을 수행하게 됩니다.

  • 사용자 이름과 비밀번호로 로그인하여 토큰을 획득

  • 토큰을 사용해 채팅방에 참여

  • 역할 권한(admin, member, guest)에 따라 메시지 전송

사전 준비 사항

1단계: 인증을 지원하도록 서버 수정

로그인 라우트와 인증 미들웨어를 서버에 추가하여, 인증된 사용자만 채팅방에 참여할 수 있도록 하고 역할 권한에 따라 메시지 전송을 제어합니다.

server.js 업데이트

server.js의 기존 내용을 다음 코드로 교체합니다.

import express from "express";
import { createServer } from "http";
import { Server } from "@mescius/js-collaboration";
import jwt from "jsonwebtoken";

const app = express();
const httpServer = createServer(app);
const server = new Server({ httpServer });

app.use(express.static("public"));
app.use(express.json());

// JWT secret key
const JWT_SECRET = "your-secret-key-here";

// Simulated user database
const users = new Map([
  ["admin1", { username: "admin1", password: "admin123", role: "admin" }],
  ["member1", { username: "member1", password: "member123", role: "member" }],
  ["guest1", { username: "guest1", password: "guest123", role: "guest" }],
]);

// Login route
app.post("/login", (req, res) => {
  const { username, password } = req.body;
  const user = users.get(username);
  if (!user || user.password !== password) {
    return res.status(401).json({ success: false, message: "Invaild username or passward." });
  }
  const token = jwt.sign({ username: user.username, role: user.role }, JWT_SECRET);
  res.json({ success: true, token, user: { username: user.username, role: user.role } });
});

// Authentication middleware
server.use("connect", async (context, next) => {
  const token = context.connection.auth?.token;
  if (!token) return await next("No token provided");
  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    context.connection.tags.set("user", { username: decoded.username, role: decoded.role });
    await next();
  } catch {
    await next("Invalid token");
  }
});

// Message permission validation middleware
server.use("message", async ({ connection }, next) => {
  const user = connection.tags.get("user");
  if (user.role === "admin" || user.role === "member") await next();
  else await next("No permission to send messages");
});

// Event handlers
server.on("connect", ({ connection }) => {
  const user = connection.tags.get("user");
  console.log(`${user.username} joined the room`);
  connection.broadcast(`${user.username} joined the chat room`, "", true);
});

server.on("message", ({ connection, data }) => {
  const user = connection.tags.get("user");
  console.log(`Received message from ${user.username}: ${data}`);
  connection.broadcast(`${user.username}: ${data}`, "", true);
});

server.on("disconnect", ({ connection }) => {
  const user = connection.tags.get("user");
  console.log(`${user.username} left the room`);
  connection.broadcast(`${user.username} left the chat room`, "", true);
});

httpServer.listen(8080, () => {
  console.log("Server running at http://localhost:8080");
});

코드 설명

  • 역할 정의:

    • admin: 최고 권한, 메시지 전송 및 채팅방 관리 가능

    • member: 일반 사용자, 메시지 송수신 가능

    • guest: 메시지 수신만 가능

  • 인증: /login 라우트를 통해 토큰을 발급받고, connect 미들웨어에서 토큰을 검증하여 사용자 정보를 저장합니다.

  • 권한: message 미들웨어를 통해 guest 사용자의 메시지 전송을 제한합니다.

2단계: 인증을 지원하도록 클라이언트 수정

클라이언트에 로그인 인터페이스를 추가하고, 로그인 후 토큰을 사용해 채팅방에 연결합니다.

  1. public/index.html 업데이트

    로그인 폼을 추가하기 위해 내용을 다음 코드로 교체합니다.

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Real-Time Chat Room (with Authentication)</title>
      <link rel="stylesheet" href="./styles.css">
      <script type="module" src="./client.bundle.js" defer></script>
    </head>
    <body>
      <div class="container">
        <h1>Real-Time Chat Room (with Authentication)</h1>
        <div id="login">
          <input type="text" id="username" placeholder="Username">
          <input type="password" id="password" placeholder="Password">
          <button onclick="login()">Login</button>
        </div>
        <div id="chat" style="display: none;"></div>
        <div class="input-area" style="display: none;">
          <input type="text" id="message" placeholder="Enter message...">
          <button onclick="sendMessage()">Send</button>
        </div>
      </div>
    </body>
    </html>
  2. public/client.js 업데이트

    로그인 및 인증 로직을 추가하기 위해 내용을 다음 코드로 교체합니다.

    import { Client } from "@mescius/js-collaboration-client";
    
    let connection = null;
    
    window.login = async function () {
      const username = document.getElementById("username").value;
      const password = document.getElementById("password").value;
      try {
        const response = await fetch("http://localhost:8080/login", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ username, password }),
        });
        const result = await response.json();
        if (result.success) {
          connectToChat(result.token);
          document.getElementById("login").style.display = "none";
          document.getElementById("chat").style.display = "block";
          document.querySelector(".input-area").style.display = "flex";
        } else {
          alert(result.message);
        }
      } catch (error) {
        alert("Login failed: " + error.message);
      }
    };
    
    function connectToChat(token) {
      const client = new Client();
      connection = client.connect("chatroom", { auth: { token } });
    
      connection.on("message", (data) => {
        const chatDiv = document.getElementById("chat");
        const message = document.createElement("p");
        message.textContent = data;
        chatDiv.appendChild(message);
        chatDiv.scrollTop = chatDiv.scrollHeight;
      });
    
      connection.on("error", (error) => {
        alert("Error: " + error.message);
      });
    }
    
    window.sendMessage = function () {
      const input = document.getElementById("message");
      const message = input.value.trim();
      if (message && connection) {
        connection.send(message);
        input.value = "";
      }
    };

    코드 설명

    1. 로그인: /login 엔드포인트를 통해 토큰을 발급받고, 로그인 성공 시 채팅방에 연결합니다.

    2. 인터페이스: 로그인 성공 시 로그인 폼을 숨기고 채팅 영역을 표시합니다.

    3. 메시지: adminmember 사용자만 서버에서 메시지가 브로드캐스트됩니다.

  3. public/styles.css 업데이트

    내용을 다음 코드로 교체합니다.

    html, body {
      height: 100%;
      margin: 0;
      font-family: Arial, sans-serif;
      background: #f0f2f5;
    }
    
    .container {
      height: 100vh;
      display: flex;
      flex-direction: column;
      background: #fff;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }
    
    h1 {
      padding: 15px;
      margin: 0;
      font-size: 24px;
      color: #333;
      text-align: center;
      border-bottom: 1px solid #ddd;
    }
    
    #login {
      padding: 20px;
      text-align: center;
    }
    
    #login input {
      padding: 10px;
      margin: 5px;
      border: 1px solid #ccc;
      border-radius: 5px;
      font-size: 14px;
    }
    
    #chat {
      flex: 1;
      overflow-y: auto;
      padding: 15px;
      background: #fafafa;
    }
    
    #chat p {
      margin: 10px 0;
      padding: 10px;
      background: #e9ecef;
      border-radius: 5px;
      word-wrap: break-word;
    }
    
    .input-area {
      display: flex;
      padding: 15px;
      border-top: 1px solid #ddd;
      background: #f9f9f9;
    }
    
    #message {
      flex: 1;
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 5px;
      font-size: 14px;
      margin-right: 10px;
    }
    
    button {
      padding: 10px 20px;
      background: #007bff;
      color: #fff;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }
    
    button:hover {
      background: #0056b3;
    }

3단계: 실행 및 테스트

  1. 클라이언트 코드 번들링

    npm run build
  2. 서버 시작

    npm run start
  3. 기능 테스트

    1. http://localhost:8080 접속

    2. 다음 사용자로 로그인:

      1. admin1 / admin123 (메시지 전송 가능)

      2. member1 / member123 (메시지 전송 가능)

      3. guest1 / guest123 (메시지 전송 불가)

    3. 여러 사용자가 참여, 메시지 전송 및 퇴장하는 실시간 동작을 확인합니다.