본문 바로가기
포스코x코딩온

[포스코x코딩온] 풀스택 부트캠프 8주차 정리 -1 (비밀번호 암호화, socket)

by 김선지 2023. 12. 13.

비밀번호 암호화

비밀번호를 데이터베이스에 바로 저장해놓으면 해킹의 위험이나 데이터베이스를 관리하는 사람이 멋대로 고객 정보를 활용할 수 있다. 그래서 비밀번호를 암호화해서 저장하는 것이 정배다.

 

단방향 암호화

복호화가 불가능하다.
그래서 비교할 때 암호화된 값을 통해서 비교한다.
서로 다른 데이터에 대해서도 같은 해시 값이 나올 수 있다.
(해시 값 충돌)
Hash 방식이 있다.

Hash: 해시 함수에 의해 얻어지는 값으로
임의의 크기의 데이터를 (레인보우 테이블을 방지하기 위해 salt나 iteration을 활용해도 됨) 고정된 크기의 데이터로 변환한다.
양방향 암호화

복호화가 가능하다.

대칭키, 공개키 방식이 있다.
데이터의 기밀성을 유지하거나 안전한 통신을 위해 사용한다.

대칭키: 암호화와 복호화에 동일한 키 사용
공개키: 공개키와 개인키라는 두개의 키 쌍 사용

 

Bcrypt

비밀번호를 암호화하는 알고리즘 중 하나로 강력하지만 해싱이 느리고 비용이 많이 든다.

 

// bcrypt
// : 비밀번호를 암호화 하는 알고리즘 중의 하나
// : 주로 강력한 보안 필요할 때 사용
// : blowfish라는 암호를 기반으로 설계된 단방향 암호화 함수

const bcrypt = require('bcrypt');

const originalPW = '1234'; // 원본 비번

const saltRounds = 10; // 솔트 라운드 수 정의, int로 12까지가 최고..?인가

// 1. 비밀번호 해싱 함수
function hashPW(password) { 
    return bcrypt.hashSync(password, saltRounds); // salt를 자동으로 생성해줌
}

// 2. 원본 비밀번호와 해시된 비밀번호가 일치하는지 확인
// 같은지, 다른지만 알려줌
function comparePW(password, hashedPW) {
    return bcrypt.compareSync(password, hashedPW);
}

// 사용 예제
// 원본 비번을 해싱한 결과
const hashedPW = hashPW(originalPW);
console.log(`Hashed PW: ${hashedPW}`);

const isMatch = comparePW(originalPW, hashedPW); // true
const isMatch2 = comparePW('heelo', hashedPW); // false
console.log('비밀번호는 같은가.', isMatch);
console.log('match2의 비밀번호는 같은가', isMatch2);

 

 


소켓(Socket)

서버와 클라이언트를 연결해주는 도구로써 인터페이스 역할을 하는 것으로 프로토콜, Ip주소, 포트넘버로 구성

TCP와 UDP프로토콜을 이용하여 데이터를 전송한다.

 

HTTP

클라이언트의 요청이 있을때만 서버가 응답하고 곧바로 연결을 종료한다.
실시간 연결이 아니다.
WebSocket

서버와 클라이언트가 특정 PORT를 통해 실시간으로 양방향 통신을 하는 방식
실시간 연결이다.

 

프로토콜이란?

컴퓨터 또는 전자기기 간의 원활한 통신을 위해 지키기로 약속한 규약. 프로토콜에는 신호 처리법, 오류처리, 암호, 인증, 주소 등을 포함한다. (이런 식으로 통신하겠다. 란 뜻)

 

TCP/IP 컴퓨터 네트워크에서 데이터 통신을 위한 프로토콜 스택으로, 네트워크 간의 데이터 교환을 가능하게 하는 중요한 기술

 

소켓 프로그래밍

- 클라이언트 소켓: 서버에 연결을 요청하고 서버에서 연결이 수락되면 서버와 데이터를 주고받을 수 있는 소켓

- 서버 소켓: 클라이언트의 요청을 받아들여 실제 통신을 위한 소켓을 생성한다.

 

 

다만 socket.id는 유저가 접속할 때마다 난수로 초기화된다. (서버가 끊겼다가 다시 연결되도 마찬가지다.) 이 점에 주의해야 할 것 같다.

exercise.ejs 파일 (Js도 한꺼번에 있다.)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <script src="/socket.io/socket.io.js"></script>
    <title>socket.io 실습</title>
  </head>
  <body>
    <header>
      <input type="text" id="username" placeholder="이름">
      <button id="enter" type="button">입장</button>
    </header>
    <main class="dnone">
      <div class="notice"></div>
        <ul class="container"></ul>
        <div class="submit">
          <select name="users" id="users">
          </select>
          <input type="text" id="text" onkeydown="if(window.event.keyCode == 13) {submitText()}" />
          <button onclick="submitText()" type="button">전송</button>
        </div>
    </main>
    <!-- 실습1 각 버튼 누를 때마다 서버로 메세지 보내기-->
    <!-- <button onclick="sayHello()">HELLO</button>
    <button onclick="sayStudy()">STUDY</button>
    <button onclick="sayBye()">BYE</button> -->
    <script>
      const socket = io.connect();
      let clients = {};
      socket.on("connect", function () {
        console.log("클라이언트 연결 완료fmwlvmwlvkmlw >", socket.id);
      });
      
      // enter 버튼을 누르면 유저 닉네임과 socket.id라는 value값을 name, socketId라는 key 값으로
      // 서버소켓에 보냄
      function enter(event) {
        event.target.disabled =true;
        const nameElement = document.querySelector('#username');
        const name = nameElement.value;
        if (nameElement.value.trim().length > 0) {
          document.querySelector('main').classList.remove('dnone');
          socket.emit('userEnter', { name : name, socketId: socket.id })
        }
      }

      document.querySelector('#enter').addEventListener('click', enter);

      // hello emit
      function submitText() {
        const pElement = document.createElement('p');
        pElement.classList.add('right');
        const gotMsg = document.createElement('span');
        
        // 채팅이 전체일 경우 (select값의 value가 전체일 때)
        if (document.querySelector('select').value === '전체') {
          gotMsg.textContent = `나: ${document.querySelector('#text').value}`;
          gotMsg.classList.add('mymsg');
          pElement.appendChild(gotMsg);
          document.querySelector('.container').appendChild(pElement);
          console.log(socket.id);
          socket.emit("text", { who: socket.id, msg: `${document.querySelector('#text').value}`});
          document.querySelector('#text').value = '';
        } else {
        // 채팅을 개인에게 보내는 경우 (select 값의 value가 전체가 아닐 때)
          console.log(clients);
          gotMsg.textContent = `(${clients[document.querySelector('#users').value]}님에게) 나: ${document.querySelector('#text').value}`;
          pElement.appendChild(gotMsg);
          document.querySelector('.container').appendChild(pElement);
          gotMsg.classList.add('secret-msg');
          socket.emit("toText", { who: socket.id, msg: `${document.querySelector('#text').value}`, to: `${document.querySelector('#users').value}`});
          document.querySelector('#text').value = '';
        }
        document.querySelector('.container').scrollTop = document.querySelector('.container').scrollHeight;
      }
		
        // 새로운 클라이언트가 들어올 때
      socket.on('notice', function(data) {
        // clients.push(socket.id);
        const divElement = document.createElement('div');
        divElement.classList.add('introduce');
        divElement.textContent = data.msg;
        document.querySelector('.notice').appendChild(divElement);

        document.querySelector('#users').innerHTML = '';
        const optionEle = document.createElement('option');
          optionEle.value = '전체';
          optionEle.textContent = '전체';
          document.querySelector('#users').appendChild(optionEle);
          clients = data.sockets
        for (const key in data.sockets) {
        // 소켓아이디(해당 클라이언트의)와 data.sockets의 key값 (socket.id들의 집합의 iteration)이 동일할 때
          if (socket.id === key) {
            continue;
          }
          const optionElement = document.createElement('option');
          optionElement.value = key;
          optionElement.textContent = data.sockets[key];
          document.querySelector('#users').appendChild(optionElement);
        }
      })

	// 전체 채팅
      socket.on('brod', function(data) {
        const pElement = document.createElement('p');
        pElement.classList.add('left');
        const gotMsg = document.createElement('span');
        gotMsg.textContent = `${clients[data.who]} : ${data.msg}`;
        gotMsg.classList.add('someones-msg')
        pElement.appendChild(gotMsg);
        document.querySelector('.container').appendChild(pElement);
        document.querySelector('.container').scrollTop = document.querySelector('.container').scrollHeight;
      })
	
    // 개인 채팅
      socket.on('toClient', function(data) {
        const pElement = document.createElement('p');
        pElement.classList.add('left');
        const gotMsg = document.createElement('span');
        gotMsg.textContent = `${data.who} : ${data.msg}`;
        gotMsg.classList.add('secret-msg');
        pElement.appendChild(gotMsg);
        document.querySelector('.container').appendChild(pElement);
        document.querySelector('.container').scrollTop = document.querySelector('.container').scrollHeight;
      });

    </script>
  </body>
</html>

 

server.js 파일

 

const express = require('express');
const http = require('http');
const socketIO = require('socket.io');

const PORT = 8000;

const app = express();

app.set('view engine', 'ejs');

// express 앱으로 http 서버 생성
const server = http.createServer(app);
// socket.id를 http 서버에 연결
const io = socketIO(server);

// app.get('/', function(req, res) {
//     res.render('chat');
// });

app.get('/exercise', function(req, res) {
    res.render('exercise');
});


app.use(express.static('public'));

const sockets = {};

// io.on(): socket 관련한 통신 작업을 처리
io.on('connection', function(socket) {
    // connection 이벤트는 클라이언트가 접속했을 때 발생
    // 콜백함수의 인자로 소켓 객체를 제공

    // socket.id : 소켓의 고유 id (브라우저 탭 단위)
    console.log('서버 연결 완료 >', socket.id);
    // socket.emit(event_name, data) : 해당 클라이언트에게만 이벤트, 데이터를 전송
    // io.emit(event_name, data): 서버에 접속된 모든 클라이언트에게 전송
    
    // io.to(소켓 아이디).emit(event_name, data) : 소켓 아이디에 해당하는 클라이언트에게만 전송
    
    // 전체 클라이언트에게 입장 안내
    socket.on('userEnter', function(data) {
        sockets[data.socketId] = data.name;
        io.emit('notice', {msg: `${data.name}님이 입장하셨습니다.`, sockets: sockets});

    })

	// 전체 메세지 받았을 때
    socket.on('text', function(data) {
        console.log(`${data.who}: ${data.msg}`);
        socket.broadcast.emit('brod', {who: data.who, msg: data.
            msg});
    })
	
    // 개인 메세지 받았을 때
    socket.on('toText', function(data) {
        io.to(data.to).emit('toClient', {who: sockets[data.who], msg:data.msg});
    });
	
    // 클라이언트가 접속을 끊었을 때
    socket.on('disconnect', function() {

        const removedId = sockets[socket.id];
        delete sockets[socket.id];
        io.emit('notice', {msg: `${removedId}님이 퇴장하셨습니다.`, sockets: sockets});
        console.log('유저 서버 연결 종료 >', socket.id);
    })
})


server.listen(PORT, function() {
    console.log(`http://localhost:${PORT}`);
})