Front-end/React

로그인 구현하기 - 3. 보안 조치와 안전성 높이기(Node.js, React)

xeunnie 2024. 7. 1. 00:07
728x90
반응형

로그인 구현 1은 프론트 과정에서 구현해야하는 툴을, 로그인 구현 2에선 JWT 토큰을 구현하는 틀을 다뤘다.
1과 2는 기본적인 틀만 다룬 것이기 때문에 실제론 더더 많은 보안 조치와 오류 처리가 필요했다.
애플리케이션의 안전성과 안정성을 높이는 것이 매우 중요하기 때문에 백과 프론트에서 적용할 수 있는 몇 가지 보안 조치와 오류 처리 방법을 다루는 글이 되겠다.

  1. 백엔드 보안 조치:
    • 환경 변수 사용
    • 강력한 비밀번호 정책
    • JWT 만료 시간 설정
    • 리프레시 토큰 사용
    • HTTPS 사용
    • 입력 데이터 검증
    • CSRF 보호
    • 로그 및 모니터링
  2. 프론트엔드 보안 조치:
    • JWT 저장 위치
    • 자동 로그아웃
    • 비동기 요청 오류 처리
    • 입력 검증
    • HTTPS 사용
    • XSS 방지

Nodejs 백엔드 보안 조치와 오류 처리

  1. 환경 변수 사용:
    JWT 시크릿 키와 같은 민감한 정보는 코드에 직접 포함하지 말고 환경 변수에 관리해야 하는데,
    dotenv 패키지를 사용하면 환경 변수를 로드할 수 있어 꽤 편리하다.

    require('dotenv').config();
    const SECRET_KEY = process.env.SECRET_KEY;
  2. npm install dotenv

  3. 강력한 비밀번호 정책:
    일단 강력한 비밀번호를 선택하도록 앞단에서 정규식으로 제지를 하는 과정이 필요하다.
    더불어 비밀번호를 해싱하고 저장할 때는 충분히 강력한 해싱 알고리즘을 사용해야 한다.

  4. const SALT_ROUNDS = 12; const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);

  5. JWT 만료 시간 설정:
    로그인 저장 기능이 있지 않은 이상 JWT 토큰에는 만료 시간을 설정하여 일정 시간 이후에는 무효화되도록 해야한다.

  6. const token = jwt.sign({ username: user.username }, SECRET_KEY, { expiresIn: '1h' });

  7. 토큰 갱신 (Refresh Token):
    액세스 토큰이 만료됐을 때 새로 갱신할 수 있게 하는 것이 리프레시 토큰이다.
    몇 분에서 몇 시간이 최대로 유효한 액세스 토큰과 달리 리프레시 토큰은 몇 주에서 몇 달 저장이 가능하고 데이터베이스에 저장도 된다.

  8. HTTPS 사용:
    데이터를 암호화하여 전송하기 위해 HTTPS를 사용하는 것이 좋다. (https 사용으로 Let's Encrypt와 같은 무료 SSL 인증서가 있다.)

  9. 입력 데이터 검증:
    사용자 입력을 철저히 검증해 SQL 인젝션, XSS(크로스 사이트 스크립팅) 등의 공격을 방지해야 한다.
    express-validator 패키지를 사용하면 입력 데이터를 검증할 수 있다.

    const { body, validationResult } = require('express-validator');
    
    app.post('/register', 
      body('username').isLength({ min: 5 }),
      body('password').isLength({ min: 5 }),
      async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
        // Registration logic
    });
  10. npm install express-validator

  11. CSRF 보호:
    CSRF(크로스 사이트 요청 위조) 공격을 방지하기 위해 CSRF 토큰을 사용해야 한다.
    공공기관 같은 보호가 중요한 사이트는 csurf 패키지를 사용해 방지하고 있다.

    const csurf = require('csurf');
    const csrfProtection = csurf({ cookie: true });
    
    app.use(csrfProtection);
  12. npm install csurf

  13. 로그 및 모니터링:
    유지보수를 위해선 서버에서 발생하는 모든 중요한 이벤트를 로그로 남기고, 이상 징후를 모니터링 해야한다.
    winston 또는 morgan과 같은 로깅 라이브러리를 사용하면 남은 로그 확인과 모니터링이 가능하다.

    const winston = require('winston');
    
    const logger = winston.createLogger({
     level: 'info',
     format: winston.format.json(),
     transports: [
       new winston.transports.File({ filename: 'error.log', level: 'error' }),
       new winston.transports.File({ filename: 'combined.log' }),
     ],
    });
  14. npm install winston

React 프론트엔드 보안 조치와 오류 처리

  1. JWT 저장 위치:
    JWT를 로컬 스토리지에 저장하지 말고, 보안이 강화된 쿠키에 저장해야 한다.
    이때 HttpOnly 플래그를 설정하면 자바스크립트로 접근할 수 없도록 할 수 있다.

  2. 자동 로그아웃:
    JWT 만료 시간을 감지하여 토큰이 만료되면 자동으로 로그아웃시키고 사용자에게 알리는 기능을 추가해야 한다.

  3. 비동기 요청 오류 처리:
    서버에서 발생한 오류를 사용자에게 알리고, 오류를 따른 대처하는 처리를 해야한다.

    const handleLogin = async (e) => {
     e.preventDefault();
     try {
       const response = await axios.post('http://localhost:5000/login', { username, password });
       setToken(response.data.token);
       localStorage.setItem('token', response.data.token);
     } catch (error) {
       console.error('Login failed:', error);
       alert('Login failed: ' + error.response.data.message);
     }
    };
  4. 입력 검증:
    사용자 입력을 클라이언트 측에서도 검증해 잘못된 데이터가 서버로 전송되지 않도록 해야 한다.

  5. HTTPS 사용:
    백엔드 처리와 마찬가지로 클라이언트와 서버 간의 통신이 암호화되도록 HTTPS를 사용해야 한다.

  6. XSS 방지:
    사용자 입력 데이터를 HTML로 렌더링할 때는 반드시 이스케이프 처리를 해야하다.
    React는 기본적으로 XSS 공격을 방지하지만, 직접 DOM 조작을 할 때는 주의해야 한다.

    // React JSX 사용 시 기본적으로 XSS 방지가 됨
    <div>{userInput}</div>
    
    // 직접 DOM 조작 시
    const element = document.createElement('div');
    element.textContent = userInput; // 이스케이프 처리
728x90
반응형