Express 개발 강좌, 권한 관리 및 사용자 역할 정의

Express.js는 Node.js를 위한 웹 프레임워크로, 사용자가 클라이언트 요청에 대해 쉽게 서버 응답을 처리할 수 있도록 만들어졌습니다. 이번 강좌에서는 Express 애플리케이션에서 권한 관리 및 사용자 역할 정의에 대해 상세히 설명하고, 실제 예제 코드를 통해 구현하는 방법을 알아보겠습니다.

1. 권한 관리의 중요성

권한 관리란 특정 리소스나 기능에 대한 접근을 제공하거나 제한하는 과정을 의미합니다. 권한 관리는 특히 다음과 같은 이유로 중요합니다:

  • 보안 강화: 적절한 권한 관리를 통해 데이터 유출이나 시스템 침해를 예방합니다.
  • 유연한 사용자 경험: 각 사용자나 그룹에 맞춤화된 경험을 제공하여 사용자의 만족도를 높입니다.
  • 법적 요구 사항 준수: 많은 산업에서 규정된 보안 기준을 충족하기 위한 요구사항이 있습니다.

2. 사용자 역할 정의

사용자 역할은 시스템 내에서 사용자가 가질 수 있는 특정 권한의 집합을 의미합니다. Roles은 일반적으로 다음과 같은 형태로 정의될 수 있습니다:


const roles = {
    ADMIN: 'admin',
    USER: 'user',
    GUEST: 'guest',
};

3. Express.js에서 권한 관리 설정하기

Express.js에서 권한 관리를 설정하기 위해서는 다음단계가 필요합니다:

  1. 사용자 인증(authenticaion) 모듈 설정
  2. 역할 기반 접근 제어(Role Based Access Control, RBAC) 구현
  3. 미들웨어 개발

3.1 사용자 인증 모듈 설정

사용자를 인증하기 위해 passport.js와 같은 인증 라이브러리를 사용할 수 있습니다. 다음은 간단한 사용자 인증 예제입니다:


const express = require('express');
const passport = require('passport');
const session = require('express-session');
const bodyParser = require('body-parser');
const LocalStrategy = require('passport-local').Strategy;

const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ secret: 'your_secret_key', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());

const users = [{ id: 1, username: 'admin', password: 'admin', role: 'admin' }];

passport.use(new LocalStrategy((username, password, done) => {
    const user = users.find(u => u.username === username && u.password === password);
    if (user) {
        return done(null, user);
    } else {
        return done(null, false, { message: 'Incorrect credentials.' });
    }
}));

passport.serializeUser((user, done) => {
    done(null, user.id);
});

passport.deserializeUser((id, done) => {
    const user = users.find(u => u.id === id);
    done(null, user);
});

app.post('/login', passport.authenticate('local', {
    successRedirect: '/dashboard',
    failureRedirect: '/login'
}));

3.2 역할 기반 접근 제어(RBAC)

RBAC는 사용자의 역할에 따라 접근 권한을 관리하는 기법입니다. 각 사용자는 특정 역할을 부여받고, 이는 사용자의 가능한 행동을 제한합니다. 다음은 역할 기반 접근 제어 미들웨어의 예입니다:


const roleAuthorization = (role) => {
    return (req, res, next) => {
        if (req.isAuthenticated() && req.user.role === role) {
            return next();
        } else {
            return res.status(403).json({ message: 'Forbidden' });
        }
    };
};

app.get('/admin', roleAuthorization('admin'), (req, res) => {
    res.send('Welcome Admin');
});

app.get('/user', roleAuthorization('user'), (req, res) => {
    res.send('Welcome User');
});

3.3 미들웨어 개발

미들웨어는 Express.js의 중요한 구성 요소로, 요청과 응답 객체를 수정하거나 요청 흐름을 제어할 수 있습니다. 권한 관리 미들웨어를 구현하는 예는 다음과 같습니다:


const authMiddleware = (req, res, next) => {
    if (req.isAuthenticated()) {
        return next(); // 인증에 성공하면 다음 미들웨어로 이동
    } else {
        return res.redirect('/login'); // 인증에 실패하면 로그인 페이지로 이동
    }
};

app.get('/dashboard', authMiddleware, (req, res) => {
    res.send('User Dashboard');
});

4. 사용자 정의 역할 확장하기

기본적인 역할 외에도 사용자의 역할을 추가하거나 변경할 수 있습니다. 예를 들어, 특정 기능에 대한 액세스 권한을 가진 editor 역할을 추가할 수 있습니다:


const roles = {
    ADMIN: 'admin',
    USER: 'user',
    GUEST: 'guest',
    EDITOR: 'editor',
};

// 역할 기준 기능 추가
app.get('/edit-content', roleAuthorization('editor'), (req, res) => {
    res.send('Edit Content Page');
});

5. 데이터베이스와 통합하기

실제 애플리케이션에서는 사용자 및 역할 정보를 데이터베이스에 저장하는 것이 중요합니다. MongoDB와 mongoose를 사용하여 사용자 인증 및 역할을 관리할 수 있습니다:


const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/yourdb', {
    useNewUrlParser: true,
    useUnifiedTopology: true
});

const userSchema = new mongoose.Schema({
    username: String,
    password: String,
    role: String
});

const User = mongoose.model('User', userSchema);

// 사용자를 데이터베이스에 저장하는 예
app.post('/register', async (req, res) => {
    const { username, password, role } = req.body;
    const newUser = new User({ username, password, role });
    await newUser.save();
    res.send('User registered!');
});

6. 결론

이번 강좌에서는 Express.js에서 권한 관리 및 사용자 역할 정의의 기초부터 DB와의 통합까지 다양하게 알아보았습니다. 권한 관리는 보안 및 운영 효율성을 위해 필수적이며, 역할 기반 접근 제어(RBAC)는 이를 위한 효과적인 방법입니다. 여러분의 Express 애플리케이션에서 이러한 기능들을 구현하여 더욱 안전하고 효율적인 시스템을 만들길 바랍니다.

7. 참고 자료

Express 개발 강좌, URL 파라미터 및 쿼리 파라미터 사용하기

웹 애플리케이션을 개발할 때, 클라이언트가 서버에 데이터를 전달하는 방법은 매우 중요합니다. 특히 Express.js와 같은 Node.js 기반의 웹 프레임워크를 사용할 때, URL 파라미터와 쿼리 파라미터를 활용하는 방법을 이해하는 것은 필수적입니다. 이번 강좌에서는 URL 파라미터와 쿼리 파라미터의 개념과 함께 사용 방법을 자세히 알아보겠습니다.

1. URL 파라미터란?

URL 파라미터는 URL 경로의 일부로, 보통 리소스를 식별하는 데 사용됩니다. 예를 들어, 사용자의 프로필 정보를 조회하기 위한 URL이 /user/123라면, 123은 사용자 ID를 나타내는 URL 파라미터입니다.

1.1 URL 파라미터의 사용 방법

Express에서 URL 파라미터를 정의하는 방법은 매우 간단합니다. HTTP 요청 라우트에서 : 기호를 사용하여 URL 파라미터를 설정할 수 있습니다. 아래는 Express를 사용하여 URL 파라미터를 처리하는 간단한 예제입니다.

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

app.get('/user/:id', (req, res) => {
    const userId = req.params.id; // URL 파라미터 접근
    res.send(`User ID: ${userId}`);
});

app.listen(port, () => {
    console.log(`Server is running on http://localhost:${port}`);
});

위 예제에서 /user/:id 라우트는 사용자의 ID를 URL 경로에서 가져옵니다. 클라이언트가 /user/123에 GET 요청을 보내면, req.params.id를 통해 123이라는 값을 얻을 수 있습니다.

2. 쿼리 파라미터란?

쿼리 파라미터는 URL의 쿼리 스트링 부분에 포함된 파라미터입니다. 일반적으로 URL의 끝에 물음표(?) 다음에 “key=value” 형식으로 추가됩니다. 예를 들어, /search?keyword=express&page=1에서 keywordpage는 쿼리 파라미터입니다.

2.1 쿼리 파라미터의 사용 방법

Express에서는 쿼리 파라미터를 req.query 객체를 통해 쉽게 접근할 수 있습니다. 다음은 쿼리 파라미터를 처리하는 예제입니다.

app.get('/search', (req, res) => {
    const keyword = req.query.keyword; // 쿼리 파라미터 접근
    const page = req.query.page || 1; // 기본값 설정
    res.send(`Searching for: ${keyword}, Page: ${page}`);
});

위 예제를 통해 /search?keyword=express&page=2에 GET 요청을 하면, req.query.keywordreq.query.page를 사용하여 각각 express2의 값을 얻을 수 있습니다.

3. URL 파라미터와 쿼리 파라미터의 차이점

  • URL 파라미터: URL 경로의 일부로, 주로 리소스를 식별하는 데 사용됩니다.
  • 쿼리 파라미터: URL의 쿼리 스트링 부분에 포함되며, 추가적인 데이터나 옵션을 전달하는 데 사용됩니다.

4. 복합적인 사용 사례

실제 웹 애플리케이션에서는 URL 파라미터와 쿼리 파라미터를 함께 사용하는 경우가 많습니다. 예를 들어, 특정 사용자 ID에 대한 게시물 목록을 조회하는 API를 만들 수 있습니다.

app.get('/user/:id/posts', (req, res) => {
    const userId = req.params.id;
    const page = req.query.page || 1; // 기본값 설정
    res.send(`Getting posts for user: ${userId}, Page: ${page}`);
});

위 코드는 특정 사용자(userId)의 게시물 목록을 쿼리 파라미터를 통해 페이지 넘버(page)와 함께 가져오는 예시입니다.

5. 실습 프로젝트

이제까지의 내용을 종합하여, 작은 실습 프로젝트를 만들어 보겠습니다. 이 프로젝트에서는 URL 파라미터와 쿼리 파라미터를 모두 사용하는 Express 애플리케이션을 구성해보겠습니다.

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

// 사용자 데이터
const users = {
    1: { name: 'Alice' },
    2: { name: 'Bob' },
};

app.get('/user/:id', (req, res) => {
    const userId = req.params.id;
    const user = users[userId];

    if (user) {
        res.send(`User found: ${user.name}`);
    } else {
        res.status(404).send('User not found');
    }
});

app.get('/user/:id/posts', (req, res) => {
    const userId = req.params.id;
    const page = req.query.page || 1;
    res.send(`Getting posts for user ${userId} on page ${page}`);
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

5.1 서버 실행 및 테스트

서버를 실행한 후, 브라우저 또는 Postman과 같은 API 클라이언트를 사용하여 다음과 같이 테스트할 수 있습니다.

  • GET /user/1 -> 사용자 정보 조회
  • GET /user/3 -> 사용자 없음
  • GET /user/1/posts?page=2 -> 사용자 1의 게시물 조회 (2페이지)

마무리

이번 강좌에서는 Express.js를 사용하여 URL 파라미터와 쿼리 파라미터를 다루는 방법에 대해 자세히 살펴보았습니다. 이러한 개념은 웹 애플리케이션에서 데이터를 효과적으로 관리하고 사용자 요청에 응답하는 데 매우 유용합니다. 더 깊이 있는 이해를 위해 다양한 예제를 시도해보고, 자신만의 애플리케이션을 구축해보기를 권장합니다.

감사합니다!

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 개발 강좌, 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. 참고 자료