Express 개발 강좌, 보안 고려사항 및 CORS 정책

작성자: 조광형

작성일: 2024년 11월 26일

1. 서론

Express는 Node.js를 기반으로 한 경량 웹 프레임워크로, 백엔드 서버 개발에 널리 사용됩니다. 그러나, 웹 애플리케이션을 개발할 때 보안은 가장 중요한 고려사항 중 하나입니다. 이 강좌에서는 Express 애플리케이션에 대한 보안 고려사항과 CORS(교차 출처 리소스 공유) 정책에 대해 자세히 알아보겠습니다.

2. Express의 보안 고려사항

애플리케이션의 보안을 강화하기 위해 다음과 같은 방법들을 적용할 수 있습니다.

2.1. HTTPS 사용

HTTP 대신 HTTPS를 사용함으로써, 데이터 전송 시 암호화를 통해 정보의 유출과 중간자 공격을 방지할 수 있습니다. Express 애플리케이션에서 HTTPS를 설정하는 방법은 다음과 같습니다.


const express = require('express');
const https = require('https');
const fs = require('fs');

const app = express();
const PORT = 3000;

// SSL 인증서 경로 설정
const options = {
    key: fs.readFileSync('/path/to/private-key.pem'),
    cert: fs.readFileSync('/path/to/certificate.pem')
};

// HTTPS 서버 생성
https.createServer(options, app).listen(PORT, () => {
    console.log(`HTTPS 서버가 포트 ${PORT}에서 실행 중입니다.`);
});
            

2.2. Helmet 미들웨어 사용

Helmet은 Express 애플리케이션의 보안을 높이기 위해 HTTP 헤더를 설정하는 미들웨어입니다. 다음과 같이 쉽게 적용할 수 있습니다.


const helmet = require('helmet');

app.use(helmet());
            

2.3. 데이터 검증 및 인가

서버에서 사용하는 입력 데이터에 대해 철저한 검증 및 인가 절차를 구현해야 합니다. 예를 들어, 사용자가 입력한 데이터에 대해 xss 필터링, SQL 인젝션 방지를 위한 Prepared Statement 등을 사용할 수 있습니다.


const { body, validationResult } = require('express-validator');

app.post('/user', [
    body('email').isEmail(),
    body('password').isLength({ min: 5 })
], (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
    }

    // 유저 생성 로직
});
            

2.4. 비밀번호 해시화

사용자 비밀번호는 평문으로 저장하지 않고 해시 처리하여 저장해야 합니다. bcrypt와 같은 라이브러리를 사용할 수 있습니다.


const bcrypt = require('bcrypt');

const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';

bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
    // 해시된 비밀번호 데이터베이스에 저장
});
            

2.5. 에러 핸들링

에러 핸들링을 통해 사용자에게 불필요한 정보가 노출되는 것을 방지할 수 있습니다. 적절한 에러 메시지를 정의하고 로그를 관리하여 보안성을 높이는 것이 좋습니다.


app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('무언가 잘못되었습니다!');
});
            

3. CORS 정책

CORS는 “Cross-Origin Resource Sharing”의 약자로, 웹 페이지가 다른 출처의 리소스에 접근할 수 있도록 해주는 메커니즘입니다. CORS 정책을 이해하고 설정하는 것은 보안과 애플리케이션의 정상 작동에 필수적입니다.

3.1. CORS의 필요성

모던 웹 애플리케이션은 종종 다양한 출처의 API와 상호작용합니다. 이때, 브라우저는 보안 상의 이유로 교차 출처 요청을 제한합니다. CORS는 이 제한을 설정하고 관리할 수 있는 방법을 제공합니다.

3.2. CORS를 설정하는 방법

Express에서 CORS를 설정할 때는 ‘cors’ 미들웨어를 사용할 수 있습니다. 설치 후 아래와 같이 설정할 수 있습니다.


const cors = require('cors');

// CORS 미들웨어 사용
app.use(cors({
    origin: 'https://example.com', // 허용할 출처
    methods: ['GET', 'POST'], // 허용할 HTTP 메서드
    allowedHeaders: ['Content-Type', 'Authorization'] // 허용할 헤더
}));
            

3.3. 특정 경로에 대한 CORS 설정

특정 경로에만 CORS를 적용하고 싶은 경우, 옵션을 지정하여 개별적으로 설정할 수 있습니다.


app.get('/api/data', cors(), (req, res) => {
    res.json({ data: '데이터입니다.' });
});
            

4. CORS 관련 보안 고려사항

CORS를 설정할 때 주의해야 할 점은 허용할 출처를 정확히 지정하는 것입니다. ‘*’와 같은 와일드카드를 사용하면 모든 도메인에서 요청할 수 있게 되므로, 필요한 경우에만 사용해야 합니다.

4.1. 요청 헤더와 메서드 제한

CORS 설정 시, 허용할 요청 헤더와 HTTP 메서드를 철저히 관리하여 보안을 강화할 수 있습니다.

4.2. Credential 지원

인증 정보를 포함한 요청을 처리해야 할 경우, ‘Access-Control-Allow-Credentials’ 헤더를 true로 설정할 수 있습니다. 다만, 요청의 출처를 제한해야 합니다.


app.use(cors({
    origin: 'https://example.com',
    credentials: true
}));
            

5. 결론

Express에서의 보안 고려사항 및 CORS 정책은 웹 애플리케이션의 안전성과 성능에 큰 영향을 미칩니다. HTTPS, Helmet, 데이터 검증, 비밀번호 해시화 등의 방법을 통해 보안을 강화하고, CORS를 적절히 설정하여 다양한 출처와 안전하게 상호작용할 수 있습니다.

앞으로의 웹 애플리케이션 개발에서도 보안을 항상 염두에 두고, 정기적으로 보안 상태를 점검하는 것이 중요합니다.

이 게시물이 유용하셨다면, 공유해 주시기 바랍니다!

Express 개발 강좌, Express.js 성능 분석 및 모니터링 도구

Express.js는 Node.js 기반의 웹 애플리케이션 프레임워크로, 쉽게 RESTful API 및 웹 애플리케이션을 구축할 수 있는 강력한 기능을 제공합니다. 그러나 웹 애플리케이션의 성능을 최적화하고 문제를 식별하기 위해서는 적절한 성능 분석 및 모니터링 도구가 필수적입니다. 이 글에서는 Express.js를 이용한 백엔드 서버의 성능 분석 및 모니터링 도구에 대해 자세히 설명하고, 예시 코드와 함께 실전에서 어떻게 활용할 수 있는지 알아보겠습니다.

1. 왜 성능 분석과 모니터링이 중요한가?

웹 애플리케이션의 성능 분석 및 모니터링은 다음과 같은 이유로 중요합니다:

  • 사용자 경험 향상: 느린 응답 시간은 사용자 경험을 저하시킬 수 있습니다. 성능 분석을 통해 필요한 최적화를 수행할 수 있습니다.
  • 해결 가능한 문제 식별: 서버 성능 저하의 원인을 식별하여 시스템 장애가 발생하기 전에 예방 조치를 취할 수 있습니다.
  • 자원 최적화: 시스템 자원을 효율적으로 사용하여 비용을 절감할 수 있습니다.
  • 애플리케이션 확장성: 추가적인 사용자가 시스템에 추가되었을 때 성능을 유지하기 위해 미리 성능을 모니터링해야 합니다.

2. Express.js 성능 분석 도구

다음은 Express.js 애플리케이션의 성능을 분석하고 모니터링하는 데 유용한 도구들입니다.

2.1. morgan

Morgan은 HTTP 요청을 로깅하는 미들웨어로, 애플리케이션의 성능을 모니터링하는 데 큰 도움이 됩니다. Morgan을 사용하면 요청 시간과 응답 상태 코드 같은 유용한 정보를 로그에 기록할 수 있습니다.

설치 및 사용 예

npm install morgan
const express = require('express');
const morgan = require('morgan');

const app = express();
app.use(morgan('combined'));  // 'combined'는 HTTP 요청 로그 형식입니다.

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

위 코드에서 ‘combined’ 형식을 설정하면, 클라이언트의 IP 주소, 요청 시간, HTTP 버전, 상태 코드 등 다양한 정보를 로그로 기록할 수 있습니다. 이를 통해 요청 및 응답 성능을 쉽게 분석할 수 있습니다.

2.2. Prometheus 및 Grafana

Prometheus는 오픈 소스 시스템 모니터링 및 경고 도구로, Grafana와 함께 사용할 수 있습니다. 이 두 도구를 결합하면, Express.js 애플리케이션의 메트릭을 시각화하고, 대시보드를 생성하여 성능을 모니터링할 수 있습니다.

설치 및 사용 예

npm install prom-client
const express = require('express');
const client = require('prom-client');

const app = express();
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics();

const httpRequestDurationSeconds = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'code'],
});

app.use((req, res, next) => {
  const end = httpRequestDurationSeconds.startTimer();
  res.on('finish', () => {
    end({ method: req.method, route: req.route?.path, code: res.statusCode });
  });
  next();
});

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.get('/metrics', (req, res) => {
  res.set('Content-Type', client.register.contentType);
  res.end(client.register.metrics());
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

위 예에서는 HTTP 요청의 지속 시간을 측정하는 메트릭을 생성하고, `/metrics` 엔드포인트를 통해 Prometheus가 수집할 수 있도록 합니다. 이를 통해 애플리케이션의 성능을 실시간으로 모니터링할 수 있습니다.

3. Express.js 성능 최적화 기법

성능 분석과 모니터링 이후에는 실질적인 성능 최적화가 필요합니다. 다음은 Express.js 애플리케이션의 성능을 최적화하기 위한 몇 가지 기법입니다.

3.1. 미들웨어 최적화

Express.js는 미들웨어를 사용하여 요청을 처리합니다. 불필요한 미들웨어가 호출되지 않도록 신중하게 관리해야 합니다. 예를 들어, 정적 파일을 제공하는 경우에는 `express.static` 미들웨어를 최상단에 배치하여 불필요한 미들웨어가 실행되지 않도록 할 수 있습니다.

3.2. 비동기 프로그래밍

Express.js에서 비동기 프로그래밍 패턴을 적극 활용하면 응답 대기 시간을 단축할 수 있습니다. async/await를 사용하여 비동기 코드를 관리하거나, 프로미스를 활용하여 코드를 간소화할 수 있습니다.

app.get('/data', async (req, res) => {
  const data = await fetchDataFromDatabase(); // 비동기 데이터베이스 호출
  res.send(data);
});

3.3. Caching

캐싱은 중복된 데이터 요청을 줄이는 좋은 방법입니다. Redis를 사용한 캐싱 전략을 도입하면 데이터를 메모리에 저장하여 성능을 향상시킬 수 있습니다.

const redis = require('redis');
const client = redis.createClient();

app.get('/data', (req, res) => {
  client.get('dataKey', async (err, data) => {
    if (data) {
      return res.send(data); // 캐시된 데이터 반환
    } else {
      const newData = await fetchDataFromDatabase();
      client.set('dataKey', newData); // 데이터 캐시에 저장
      return res.send(newData);
    }
  });
});

4. 결론

Express.js 애플리케이션의 성능을 분석하고 모니터링하는 것은 성공적인 애플리케이션 운영을 위해 필수적입니다. morgan, Prometheus, Grafana와 같은 도구를 활용하여 성능 데이터를 수집하고, 이를 바탕으로 미들웨어 최적화, 비동기 프로그래밍 및 캐싱 전략을 수립할 수 있습니다. 이러한 방법들을 통해 애플리케이션의 성능을 크게 향상시킬 수 있습니다. 독자 여러분도 오늘 소개한 방법들을 직접 시험해보시길 바랍니다.

Express 개발 강좌, 세션 및 쿠키를 이용한 사용자 인증

웹 애플리케이션에서 사용자 인증은 매우 중요한 요소입니다. 사용자가 자신을 확인할 수 있도록 해주며, 이는 개인화된 경험과 보안을 제공합니다. 이 강좌에서는 Express 프레임워크를 사용하여 세션과 쿠키를 활용한 사용자 인증 시스템을 구축하는 방법에 대해 설명합니다. 이를 위해 express-sessioncookie-parser 미들웨어를 사용할 것입니다.

1. Express 및 관련 모듈 설치

개발을 시작하기 위해 우선 Express와 필요한 미들웨어를 설치해야 합니다. npm을 사용하여 다음과 같이 설치합니다:

        npm install express express-session cookie-parser body-parser
    

여기서 설치된 각 패키지의 역할은 다음과 같습니다:

  • express: Node.js를 위한 간단한 웹 애플리케이션 프레임워크
  • express-session: 세션 미들웨어로, 클라이언트의 세션을 관리합니다
  • cookie-parser: 쿠키를 쉽게 파싱할 수 있게 해주는 미들웨어입니다
  • body-parser: 요청 본문(body)을 파싱할 수 있도록 도와줍니다

2. 기본 Express 서버 설정

기본 Express 서버를 설정하는 방법은 간단합니다. 서버 파일을 생성하고 필요한 모듈을 임포트한 후, 서버를 시작하는 코드를 작성해 보겠습니다.

        
const express = require('express');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');

const app = express();
const PORT = 3000;

// 미들웨어 설정
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({
    secret: '비밀 키', // 세션을 암호화하는 데 사용할 키
    resave: false,
    saveUninitialized: true,
    cookie: { secure: false } // SSL 사용시 true로 변경
}));

// 기본 라우터 설정
app.get('/', (req, res) => {
    res.send('Welcome to the Express App');
});

app.listen(PORT, () => {
    console.log(`서버가 http://localhost:${PORT}에서 실행 중입니다.`);
});
        
    

위 코드를 실행하면 기본 Express 서버가 준비됩니다. 이제 사용자가 인증할 수 있도록 세션을 설정해 보겠습니다.

3. 사용자 인증 라우트 만들기

사용자 인증을 위해 로그인 및 로그아웃 라우트를 만듭니다. 사용자는 로그인할 때 세션이 생성되고 로그아웃하면 세션이 삭제됩니다.

        
// 로그인 라우터
app.post('/login', (req, res) => {
    const { username, password } = req.body;

    // 간단한 사용자 인증 (실제로는 데이터베이스를 확인해야 함)
    if (username === 'user' && password === 'password') {
        req.session.user = username; // 세션에 사용자 정보를 저장
        res.send('로그인 성공!');
    } else {
        res.send('로그인 실패: 잘못된 사용자 이름 또는 비밀번호');
    }
});

// 로그아웃 라우터
app.post('/logout', (req, res) => {
    req.session.destroy((err) => {
        if (err) {
            return res.send('로그아웃 실패');
        }
        res.send('로그아웃 성공');
    });
});
        
    

위의 코드는 간단한 로그인 프로세스를 보여줍니다. 실제 애플리케이션에서는 데이터베이스에서 사용자 정보를 확인하는 것이 좋습니다. 이 세션 정보를 통해 사용자가 로그인 상태인지 여부를 확인할 수 있습니다.

4. 사용자가 로그인했는지 확인하기

사용자가 로그인했는지를 확인하는 미들웨어를 작성해 보겠습니다. 이 미들웨어는 특정 라우트에 접근하기 전에 호출되어 사용자의 인증 상태를 확인합니다.

        
// 인증 미들웨어
function isAuthenticated(req, res, next) {
    if (req.session.user) {
        return next(); // 로그인한 상태
    } else {
        res.status(401).send('로그인 필요');
    }
}

// 보호된 라우트
app.get('/dashboard', isAuthenticated, (req, res) => {
    res.send(`환영합니다, ${req.session.user}님! 대시보드에 오신 것을 환영합니다.`);
});
        
    

이제 /dashboard 경로로 접근할 때 사용자 인증 상태를 확인할 수 있습니다. 로그인하지 않은 사용자는 401 오류 메시지를 받게 됩니다.

5. 쿠키를 이용한 사용자 인증

사용자가 로그인할 때 쿠키를 설정하여 인증을 유지하는 방법도 있습니다. 쿠키에는 사용자 정보를 저장할 수 있으며, 클라이언트 측에서 쉽게 접근이 가능합니다.

        
app.post('/login', (req, res) => {
    const { username, password } = req.body;

    if (username === 'user' && password === 'password') {
        req.session.user = username;
        res.cookie('username', username, { 
            maxAge: 900000, // 쿠키 만료 시간
            httpOnly: true // JavaScript에서 액세스 금지
        });
        res.send('로그인 성공!');
    } else {
        res.send('로그인 실패: 잘못된 사용자 이름 또는 비밀번호');
    }
});
        
    

위의 코드는 로그인 성공 시 쿠키를 설정하는 방법을 보여줍니다. 쿠키의 속성으로는 maxAgehttpOnly를 설정했습니다. httpOnly 속성은 JavaScript에서 액세스할 수 없도록 하여 보안을 강화합니다.

6. 쿠키와 세션의 차이점

쿠키와 세션은 둘 다 사용자 인증에 사용되지만, 그 동작 방식은 다릅니다:

  • 쿠키: 클라이언트 측에 저장되며, 사용자의 브라우저에 남아 있는 데이터입니다. 따라서 사용자가 브라우저를 종료하더라도 쿠키는 만료 시까지 유지됩니다.
  • 세션: 서버 측에 저장되며, 보통 사용자가 로그인하는 동안 유지됩니다. 사용자가 로그아웃하거나 세션이 만료되면 삭제됩니다.

쿠키는 클라이언트 측에서 쉽게 접근할 수 있는 반면, 세션은 서버에서 안전하게 관리되므로 보안이 강화됩니다.

7. 보안 강화하기

사용자 인증을 할 때 보안을 강화하는 것은 매우 중요합니다. 다음은 몇 가지 보안 수칙입니다:

  • HTTPS 사용: 모든 트래픽을 암호화하여 중간자 공격을 방지합니다.
  • 세션 무작위화: 세션 ID를 무작위로 생성하여 예측할 수 없도록 합니다.
  • 최소 권한 원칙: 사용자가 필요한 권한만 부여하여 시스템에 대한 접근을 최소화합니다.
  • 비밀번호 해시화: 사용자의 비밀번호를 해시 알고리즘(예: bcrypt)을 사용하여 안전하게 저장합니다.

8. 결론

이번 강좌에서는 Express 프레임워크를 이용하여 세션 및 쿠키를 활용한 사용자 인증 방법에 대해 알아보았습니다. 기본적인 인증 시스템을 구축하는 데 필요한 코드와 원리를 이해했기를 바랍니다. 사용자 인증은 웹 애플리케이션의 중요한 구성 요소이며, 안전하고 효율적인 방법으로 구현해야 합니다. 이제 여러분은 세션과 쿠키를 사용하여 사용자 인증 시스템을 구축하고 보안을 강화하는 방법을 배웠으니, 이를 바탕으로 더욱 발전된 기능을 구현해 보시기 바랍니다.

Tip: 사용자 인증 시스템을 구성할 때는 항상 최신 보안 표준을 따르고, 정기적으로 코드를 검토하여 취약점을 방지하는 것이 중요합니다.

9. 추가 자료

더 깊은 이해를 원하신다면 다음과 같은 자료들을 참고하시기 바랍니다:

10. Q&A

질문이 있으시면 댓글에 남겨주세요. 함께 해결해 보도록 하겠습니다!

Express 개발 강좌, JWT(JSON Web Token) 기반 인증 구현

오늘날 웹 애플리케이션에서 사용자 인증은 매우 중요한 요소 중 하나입니다. 특히 RESTful API를 사용하는 애플리케이션에서는 상태를 유지하지 않는 인증 방식이 필요합니다. 여기에서 JWT(JSON Web Token)는 유용한 도구로 활용됩니다. 이 강좌에서는 Node.js의 Express 프레임워크를 이용하여 JWT 기반 인증 시스템을 구현하는 방법에 대해 살펴보겠습니다. 이번 글에서는 JWT의 개념, Express 서버 설정, 사용자 등록 및 로그인 API 구현, 보호된 경로 설정 및 클라이언트 요청 처리 등을 포함하여 종합적인 내용을 다룰 것입니다.

1. JWT란 무엇인가?

JWT는 JSON Web Token의 약자로, 주로 클라이언트와 서버 간의 정보를 안전하게 전송하기 위해 사용되는 개방형 표준입니다. JWT는 세 부분으로 구성되어 있습니다:

  • Header: 토큰의 메타데이터를 포함하고 있는 부분입니다. 일반적으로 토큰의 타입(JWT)과 사용된 알고리즘(예: HMAC SHA256)을 지정합니다.
  • Payload: 인증에 필요한 정보를 담고 있는 부분으로, 사용자 ID와 같은 정보를 포함합니다. 이 데이터는 JSON 형식으로 인코딩됩니다.
  • Signature: Header와 Payload를 조합하여 서명한 부분입니다. 이를 통해 데이터가 변조되지 않았음을 확인할 수 있습니다.

1.1 JWT의 장점

JWT는 여러 가지 장점을 제공합니다:

  • 상태 비저장: 서버는 클라이언트의 상태를 저장할 필요가 없습니다.
  • 보안성: 서명 과정을 통해 나중에 토큰이 변조되지 않았음을 증명할 수 있습니다.
  • 접근성: JWT는 JSON 형식으로 되어 있어 다양한 프로그래밍 언어에서 쉽게 사용할 수 있습니다.

2. Express 서버 환경 설정

Express.js와 관련 패키지를 설치하고 기본적인 서버를 설정하는 방법을 알아보겠습니다.

2.1. Node.js 및 Express 설치

우선 Node.js가 설치되어 있어야 합니다. Node.js 공식 홈페이지 에서 설치할 수 있습니다. Node와 NPM이 설치된 후, 다음 명령어를 통해 Express와 필요한 패키지를 설치합니다:

npm init -y
npm install express jsonwebtoken bcryptjs body-parser cors

2.2. 기본 서버 설정

먼저, 기본 Express 서버를 설정해 보겠습니다. `server.js`라는 파일을 만들고 아래의 코드를 입력합니다:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 5000;

// 미들웨어
app.use(cors());
app.use(bodyParser.json());

// 기본 라우트
app.get('/', (req, res) => {
    res.send('Hello, Express with JWT!');
});

// 서버 시작
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

3. 사용자 등록 및 로그인 API 구현

이제 사용자 등록과 로그인을 위한 API를 구현하겠습니다. 간단한 사용자 저장소를 위해 메모리에 간단한 배열을 사용할 것입니다. 실제 애플리케이션에서는 데이터베이스를 사용하는 것이 좋습니다.

3.1. 사용자 데이터 모델

사용자 데이터를 저장하기 위해 다음과 같은 구조를 사용할 것입니다:

let users = [];

3.2. 비밀번호 해시화

사용자의 비밀번호는 저장하기 전에 안전하게 해시화해야 합니다. 이를 위해 `bcryptjs` 패키지를 사용하겠습니다. 아래는 사용자 등록 API입니다:

app.post('/api/register', async (req, res) => {
    const { username, password } = req.body;

    // 비밀번호 해시화
    const hashedPassword = await bcrypt.hash(password, 10);

    // 사용자 등록
    const newUser = { username, password: hashedPassword };
    users.push(newUser);

    res.status(201).send('User registered successfully');
});

3.3. 로그인 API 구현

로그인 API는 사용자가 제공한 정보에 따라 JWT를 생성하는 역할을 합니다:

app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;
    const user = users.find(user => user.username === username);

    if (!user) {
        return res.status(400).send('Invalid credentials');
    }

    // 비밀번호 확인
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
        return res.status(400).send('Invalid credentials');
    }

    // JWT 생성
    const token = jwt.sign({ username: user.username }, 'secretKey', { expiresIn: '1h' });
    res.json({ token });
});

4. 보호된 경로 설정

JWT를 이용해 인증된 사용자만 접근 가능한 보호된 경로를 설정하겠습니다. 먼저 JWT를 검증하는 미들웨어를 작성합니다:

function authenticateToken(req, res, next) {
    const token = req.headers['authorization'] && req.headers['authorization'].split(' ')[1];

    if (!token) return res.sendStatus(401);

    jwt.verify(token, 'secretKey', (err, user) => {
        if (err) return res.sendStatus(403);
        req.user = user;
        next();
    });
}

4.1 보호된 API 예제

인증된 사용자만 접근할 수 있는 보호된 API를 만들어 보겠습니다:

app.get('/api/protected', authenticateToken, (req, res) => {
    res.send(`Hello, ${req.user.username}! This is a protected route.`);
});

5. 클라이언트 요청 처리

이제 클라이언트에서 위의 API를 호출하여 테스트해 보겠습니다. 예를 들어, `fetch`를 사용하여 사용자 등록 및 로그인 요청을 보낼 수 있습니다:

async function register(username, password) {
    const response = await fetch('http://localhost:5000/api/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password }),
    });
    return response.text();
}

async function login(username, password) {
    const response = await fetch('http://localhost:5000/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password }),
    });
    const { token } = await response.json();
    localStorage.setItem('token', token);
    return token;
}

async function accessProtected() {
    const token = localStorage.getItem('token');
    const response = await fetch('http://localhost:5000/api/protected', {
        headers: { 'Authorization': `Bearer ${token}` },
    });
    return response.text();
}

6. 결론

이제 간단한 Express 서버에서 JWT 기반 인증을 구현하는 방법을 배웠습니다. 이 과정을 통해 JWT의 개념, 사용자 등록 및 로그인 API 구현, 보호된 경로 설정 및 클라이언트 요청 처리 등을 익혔습니다. 실제 프로젝트에서는 데이터베이스와 더 많은 기능을 추가하여 발전시킬 수 있습니다. JWT를 사용하면 애플리케이션의 보안성을 높이고, 확장성을 확보할 수 있습니다. 더 나아가 OAuth와 같은 인증 체계와 연계하여 더욱 발전된 시스템을 구축할 수 있습니다.

7. 참고 자료

Express 개발 강좌, 커스텀 미들웨어 및 플러그인 개발하기

Node.js의 Express는 웹 애플리케이션 개발을 위한 강력하고 유연한 프레임워크입니다. Express의 가장 큰 장점之一은 미들웨어(middleware)를 사용하여 요청과 응답을 처리할 수 있는 기능입니다. 이 글에서는 Express에서 커스텀 미들웨어와 플러그인을 개발하는 방법을 자세히 살펴보겠습니다.

1. Express 미들웨어란?

미들웨어는 요청(Request)과 응답(Response) 객체 사이에서 실행되는 함수입니다. 이 함수는 요청을 처리하거나 요청/응답 객체를 변경할 수 있는 기능을 제공합니다. Express에서 미들웨어는 다음과 같은 역할을 합니다:

  • 요청 로그
  • 요청 데이터 파싱
  • 인증 및 권한 부여
  • 에러 처리
  • 응답 처리

2. 커스텀 미들웨어 만들기

안정적인 웹 애플리케이션을 구축하기 위해 커스텀 미들웨어를 작성하는 것이 중요합니다. 커스텀 미들웨어의 기본 구조는 다음과 같습니다:

function customMiddleware(req, res, next) {
    // 요청 처리
    console.log('요청 정보:', req.method, req.url);
    
    // 다음 미들웨어로 넘어가기
    next();
}

2.1. 미들웨어의 등록

커스텀 미들웨어는 app.use() 메서드를 사용하여 등록할 수 있습니다. 예를 들어, 다음은 기본적인 Express 서버에 커스텀 미들웨어를 등록하는 방법입니다:

const express = require('express');
const app = express();

// 커스텀 미들웨어 정의
function customMiddleware(req, res, next) {
    console.log('요청 정보:', req.method, req.url);
    next();
}

// 미들웨어 등록
app.use(customMiddleware);

// 라우트 정의
app.get('/', (req, res) => {
    res.send('안녕하세요, Express!');
});

// 서버 시작
app.listen(3000, () => {
    console.log('서버가 http://localhost:3000 에서 실행 중입니다.');
});

3. 미들웨어의 흐름

Express에서 미들웨어는 요청 파이프라인의 일부로 작동합니다. 요청이 들어온 순서대로 미들웨어가 실행됩니다. 다음은 미들웨어가 어떻게 흐르는지를 설명합니다:

  • 클라이언트에서 요청을 보냅니다.
  • 서버는 미들웨어를 통해 요청을 처리합니다.
  • next() 호출로 다음 미들웨어로 넘어갑니다.
  • 모든 미들웨어가 실행된 후, 최종 라우트 핸들러가 실행되어 응답을 클라이언트에 반환합니다.

4. 커스텀 플러그인 개발하기

Express의 플러그인은 미들웨어와 비슷하지만 특정 기능이나 모듈을 위해 설계된 일반적인 기능입니다. 커스텀 플러그인을 만들기 위해 아래의 단계를 따르면 됩니다.

4.1. 플러그인 구조

플러그인은 보통 아래의 형식을 가지고 있습니다:

module.exports = function(options) {
    return function(req, res, next) {
        // 플러그인 로직
        next();
    };
};

4.2. 플러그인 예제

다음은 요청에 대해 특정 검증을 추가하는 간단한 플러그인을 만드는 예제입니다:

function validateRequest(options) {
    return function(req, res, next) {
        const apiKey = req.headers['x-api-key'];
        
        if (apiKey === options.apiKey) {
            next(); // 올바른 API 키가 제공되면 다음으로 진행
        } else {
            res.status(403).send('Forbidden: Invalid API key');
        }
    };
}

// 플러그인 사용 예
const express = require('express');
const app = express();

const apiKeyPlugin = validateRequest({ apiKey: 'my-secret-key' });
app.use(apiKeyPlugin);

// 라우트 정의
app.get('/', (req, res) => {
    res.send('안녕하세요, 올바른 API 키가 있습니다!');
});

app.listen(3000, () => {
    console.log('서버가 http://localhost:3000 에서 실행 중입니다.');
});

5. 에러 처리 미들웨어

Express에서 에러 처리는 다른 미들웨어와 동일하게 처리되지만, 에러 처리 미들웨어의 첫 번째 매개변수는 에러 객체입니다. 에러 처리 미들웨어는 다음과 같이 정의합니다:

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('무언가 잘못되었습니다!');
});

5.1. 에러 처리 미들웨어 예제

아래는 간단한 에러 발생 예제를 포함한 Express 서버입니다:

const express = require('express');
const app = express();

// 라우트 정의
app.get('/', (req, res) => {
    res.send('안녕하세요, Express!');
});

// 의도적인 에러 발생
app.get('/error', (req, res, next) => {
    next(new Error('의도적인 에러 발생'));
});

// 에러 처리 미들웨어
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('무언가 잘못되었습니다!');
});

// 서버 시작
app.listen(3000, () => {
    console.log('서버가 http://localhost:3000 에서 실행 중입니다.');
});

6. 로깅 미들웨어 예제

로그를 남기는 것은 애플리케이션의 문제를 진단하는 데 핵심적인 역할을 합니다. 다음은 요청을 기록하는 커스텀 로깅 미들웨어의 예입니다:

function loggingMiddleware(req, res, next) {
    const currentDate = new Date().toISOString();
    console.log(`[${currentDate}] ${req.method} ${req.url}`);
    next();
}

// 서버 설정
const express = require('express');
const app = express();
app.use(loggingMiddleware);

// 라우트 정의
app.get('/', (req, res) => {
    res.send('안녕하세요, Express!');
});

// 서버 시작
app.listen(3000, () => {
    console.log('서버가 http://localhost:3000 에서 실행 중입니다.');
});

7. 성능 최적화를 위한 미들웨어

미들웨어는 애플리케이션의 성능에 영향을 미칠 수 있습니다. 다음은 성능을 최적화하기 위한 몇 가지 팁입니다:

  • 미들웨어는 필요한 경우에만 등록하십시오.
  • 불필요한 요청을 방지하기 위해 조건부 미들웨어를 사용하십시오.
  • 프로덕션 환경에서는 개발용 미들웨어를 비활성화하십시오.

8. 결론

이번 글에서는 Express에서 커스텀 미들웨어 및 플러그인을 개발하는 방법에 대해 알아보았습니다. 미들웨어를 통해 요청 처리 과정을 유연하게 제어할 수 있으며, 이를 통해 보다 효율적이고 유지보수하기 쉬운 애플리케이션을 개발할 수 있습니다. 다양한 예제를 통해 미들웨어의 작성과 등록, 사용 방법을 살펴보았으니, 여러분의 프로젝트에 이러한 미들웨어를 적용해 보시기 바랍니다.

더 나아가, Express의 기능을 활용하여 자신만의 플러그인을 개발하고, 필요에 따라 서버를 최적화하는 방법을 고민해 보시기 바랍니다. 더 복잡한 애플리케이션에서는 다수의 미들웨어가 상호작용하며, 이러한 상호작용을 이해하는 것이 중요합니다. 커스텀 미들웨어를 활용하여 기능을 확장하고, 성능을 최적화하는 강력한 Express 애플리케이션을 만들어 보십시오.