2026년 5월 21일

인증(Authentication)과 인가(Authorization) 마스터하기: 현대 시스템 보안의 두 기둥

90
인증(Authentication)과 인가(Authorization) 마스터하기: 현대 시스템 보안의 두 기둥

인증(Authentication)과 인가(Authorization) 마스터하기: 현대 시스템 보안의 두 기둥

인증(Authentication)과 인가(Authorization) 마스터하기: 현대 시스템 보안의 두 기둥

안녕하세요, 10년 경력의 소프트웨어 엔지니어이자 기술 교육자입니다. 오늘 우리가 다룰 주제는 모든 시스템 보안의 가장 기본적인 토대이자, 초중급 개발자들이 흔히 혼동하거나 간과하기 쉬운 '인증(Authentication)'과 '인가(Authorization)'입니다. 이 두 가지 개념을 명확히 이해하고 올바르게 적용하는 것은 여러분이 만드는 애플리케이션의 신뢰성과 보안을 결정짓는 핵심 요소입니다.

1. 개념 소개: 정의, 탄생 배경, 왜 중요한지

1. 개념 소개: 정의, 탄생 배경, 왜 중요한지

인증(Authentication)이란?

인증은 "당신이 누구인지"를 확인하는 과정입니다. 가장 흔한 예로는 웹사이트 로그인 시 아이디와 비밀번호를 입력하는 것입니다. 시스템은 여러분이 제공한 정보를 기반으로 여러분이 주장하는 신원(identity)이 맞는지 검증합니다. 마치 공항에서 여권을 제시하여 본인임을 증명하는 것과 같습니다. 지문, 얼굴 인식, OTP(One-Time Password) 등 다양한 방법이 사용될 수 있습니다.

인가(Authorization)란?

인가는 "당신이 무엇을 할 수 있는지"를 확인하는 과정입니다. 인증을 통해 사용자의 신원이 확인된 후, 시스템은 해당 사용자가 특정 리소스(데이터, 기능, 페이지 등)에 접근하거나 특정 작업을 수행할 권한이 있는지 판단합니다. 호텔 체크인 후 받은 키 카드가 여러분에게 특정 객실에 들어갈 권한을 주는 것과 같습니다. 모든 객실에 들어갈 수 있는 것은 아니죠.

탄생 배경

초기 컴퓨터 시스템은 단일 사용자 환경이거나, 소수의 신뢰할 수 있는 사용자만을 대상으로 했습니다. 하지만 인터넷의 등장과 함께 수많은 사용자가 동시에 여러 시스템에 접근하게 되면서, 누가 누구이고 무엇을 할 수 있는지 통제해야 할 필요성이 커졌습니다. 민감한 정보의 유출이나 시스템 오용을 막기 위해, 인증과 인가는 현대 시스템 보안의 필수적인 구성 요소로 자리 잡았습니다.

왜 중요한가?

  1. 데이터 보호: 민감한 사용자 데이터나 기업 기밀 정보가 인가되지 않은 사용자에게 노출되는 것을 방지합니다.
  2. 시스템 무결성 유지: 권한 없는 사용자가 시스템의 핵심 기능을 변경하거나 파괴하는 것을 막아 시스템의 안정성과 신뢰성을 보장합니다.
  3. 규제 준수: GDPR, HIPAA 등 많은 데이터 보호 및 개인정보 보호 규제는 강력한 인증 및 인가 시스템을 요구합니다.
  4. 사용자 신뢰: 사용자는 자신의 정보가 안전하게 보호되고 있음을 확신할 때 해당 서비스를 신뢰하고 지속적으로 사용합니다.

쉽게 비유하자면, 인증은 "회사에 들어갈 수 있는 사람인지" 확인하는 출입문이고, 인가는 "그 사람이 어느 부서에 가서 어떤 업무를 처리할 수 있는지" 결정하는 사원증의 권한 범위라고 할 수 있습니다.

2. 핵심 원리 설명 (비유와 다이어그램 활용)

2. 핵심 원리 설명 (비유와 다이어그램 활용)

인증과 인가는 보통 연속적인 과정으로 발생합니다. 먼저 인증을 통해 사용자의 신원을 확인하고, 그 신원을 바탕으로 인가 결정을 내립니다.

인증의 핵심 원리

사용자가 자신의 신원(ID)과 증명 정보(비밀번호, 생체 정보 등)를 제출하면, 시스템은 저장된 정보와 비교하여 일치하는지 확인합니다. 성공적으로 인증되면, 시스템은 해당 사용자가 누구인지를 나타내는 신뢰할 수 있는 증명을 발행합니다. 이 증명은 일반적으로 세션 쿠키액세스 토큰(Access Token) 형태를 띠며, 이후 사용자의 모든 요청에 첨부되어 사용자의 신원을 간접적으로 증명하는 데 사용됩니다.

  • 비유: 공항에서 여권을 보여주면, 심사관이 여권이 진짜인지, 본인 얼굴과 일치하는지 확인합니다. 확인이 끝나면, 여러분은 공항 내부로 들어갈 수 있는 '승객'이라는 신분을 얻게 됩니다.

인가의 핵심 원리

인가는 인증 과정에서 얻은 사용자의 신원 정보(예: 사용자 ID, 역할, 그룹, 특정 속성)를 바탕으로, 요청된 리소스나 작업에 대한 접근 규칙을 적용하여 허용 여부를 결정합니다. 인가를 구현하는 방식은 다양하지만, 가장 흔한 두 가지는 다음과 같습니다.

  1. 역할 기반 접근 제어 (Role-Based Access Control, RBAC): 사용자에게 '관리자', '일반 사용자', '게스트'와 같은 역할을 부여하고, 각 역할에 특정 권한(예: '게시물 작성', '사용자 관리', '데이터 조회')을 할당합니다. 사용자는 자신이 가진 역할에 따라 허용된 작업을 수행할 수 있습니다.
  2. 속성 기반 접근 제어 (Attribute-Based Access Control, ABAC): 사용자의 속성(예: 소속 부서, 직책, 위치), 리소스의 속성(예: 데이터 민감도, 소유자), 환경 속성(예: 시간, IP 주소) 등을 종합적으로 고려하여 접근을 제어합니다. RBAC보다 훨씬 유연하고 세밀한 제어가 가능하지만, 구현이 복잡합니다.
  • 비유: 공항 내부로 들어온 '승객'이라는 신분을 가진 여러분은, 이코노미 클래스 티켓을 가지고 있다면 이코노미 라운지에는 들어갈 수 있지만, 퍼스트 클래스 라운지에는 들어갈 수 없습니다. 티켓(역할 또는 속성)이 여러분의 접근 권한(인가)을 결정하는 것입니다.

개념적 흐름 다이어그램 (설명)

  1. 사용자 (클라이언트): 웹 브라우저나 모바일 앱을 통해 시스템에 접속합니다.
  2. 인증 요청: 사용자 ID와 비밀번호를 인증 서버에 보냅니다.
  3. 인증 서버: 사용자 정보를 검증하고, 성공하면 액세스 토큰 (예: JWT)을 발행하여 사용자에게 전달합니다.
  4. 인가 요청: 사용자는 액세스 토큰을 포함하여 보호된 리소스(예: /api/users/profile)에 대한 요청을 보냅니다.
  5. API 게이트웨이 / 미들웨어: 들어오는 모든 요청을 가로채서 액세스 토큰의 유효성을 검사합니다 (토큰이 유효한지, 만료되지 않았는지 등).
    • 토큰 유효성 검사 (인증): 토큰이 유효하면, 토큰 내부의 사용자 ID와 역할을 추출합니다.
    • 권한 확인 (인가): 추출된 사용자 ID와 역할이 요청된 리소스에 접근할 권한이 있는지 확인합니다.
  6. 리소스 서버: 인가 검사가 통과되면, 요청된 리소스를 사용자에게 제공합니다.

이 과정에서 API 게이트웨이나 애플리케이션의 미들웨어 계층이 인증과 인가 로직을 처리하는 핵심적인 역할을 수행합니다.

3. 코드 예제 2개 (Python 또는 JavaScript, 주석 포함)

예제 1: Python (Flask를 이용한 간단한 역할 기반 인가 데코레이터)

이 예제는 Flask 웹 프레임워크에서 특정 역할(role)을 가진 사용자만 접근할 수 있도록 하는 간단한 데코레이터를 구현합니다. 사용자의 역할은 session이나 JWT 토큰에서 얻었다고 가정합니다.

from flask import Flask, request, jsonify, session, redirect, url_for
from functools import wraps

app = Flask(__name__)
app.secret_key = 'super_secret_key' # 실제 프로덕션에서는 환경 변수로 관리

# 가상의 사용자 데이터베이스
USERS = {
    "admin": {"password": "admin_password", "roles": ["admin", "user"]},
    "user1": {"password": "user1_password", "roles": ["user"]},
    "guest": {"password": "guest_password", "roles": ["guest"]},
}

# --- 인증 부분 (간단한 세션 기반) ---
@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')

    user_info = USERS.get(username)
    if user_info and user_info['password'] == password:
        session['logged_in_user'] = username
        session['user_roles'] = user_info['roles'] # 인증 후 사용자 역할 세션에 저장
        return jsonify({"message": f"Welcome, {username}!"}), 200
    return jsonify({"message": "Invalid credentials"}), 401

@app.route('/logout')
def logout():
    session.pop('logged_in_user', None)
    session.pop('user_roles', None)
    return jsonify({"message": "Logged out"}), 200

# --- 인가 부분 (역할 기반 데코레이터) ---
def roles_required(allowed_roles):
    """
    특정 역할(들)을 가진 사용자만 접근을 허용하는 인가 데코레이터.
    """
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if 'logged_in_user' not in session:
                return jsonify({"message": "Authentication required"}), 401 # 인증되지 않은 사용자
            
            user_roles = session.get('user_roles', [])
            
            # 사용자가 허용된 역할 중 하나라도 가지고 있는지 확인
            if not any(role in user_roles for role in allowed_roles):
                return jsonify({"message": "Authorization denied: Insufficient roles"}), 403 # 인가 실패
            
            return f(*args, **kwargs)
        return decorated_function
    return decorator

@app.route('/admin_panel')
@roles_required(['admin']) # 'admin' 역할만 접근 가능
def admin_panel():
    return jsonify({"message": f"Welcome to the admin panel, {session['logged_in_user']}!"}), 200

@app.route('/user_dashboard')
@roles_required(['user', 'admin']) # 'user' 또는 'admin' 역할만 접근 가능
def user_dashboard():
    return jsonify({"message": f"Welcome to your dashboard, {session['logged_in_user']}!"}), 200

@app.route('/public_info')
def public_info():
    return jsonify({"message": "This is public information, no authentication/authorization needed."}), 200

if __name__ == '__main__':
    app.run(debug=True)

설명:

  • /login 엔드포인트는 사용자 이름과 비밀번호로 인증을 수행하고, 성공하면 세션에 사용자 정보와 역할을 저장합니다.
  • roles_required 데코레이터는 요청된 리소스에 접근하기 전에 사용자가 인증되었는지 (logged_in_user 세션 존재 여부) 확인하고, 세션에 저장된 user_roles가 데코레이터에 명시된 allowed_roles 중 하나라도 포함하는지 확인합니다.
  • /admin_panel은 'admin' 역할만, /user_dashboard는 'user' 또는 'admin' 역할만 접근할 수 있도록 인가 로직이 적용되어 있습니다.

예제 2: JavaScript (Node.js Express를 이용한 JWT 기반 인증 및 역할 기반 인가 미들웨어)

이 예제는 Node.js Express 애플리케이션에서 JWT(JSON Web Token)를 사용하여 사용자 인증을 처리하고, 토큰에 포함된 역할을 기반으로 인가를 수행하는 미들웨어를 구현합니다.

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

app.use(express.json()); // JSON 요청 본문 파싱

const JWT_SECRET = 'your_jwt_secret_key'; // 실제 프로덕션에서는 환경 변수로 관리

// 가상의 사용자 데이터베이스
const USERS = {
    "admin": { password: "admin_password", roles: ["admin", "user"] },
    "user1": { password: "user1_password", roles: ["user"] },
    "guest": { password: "guest_password", roles: ["guest"] },
};

// --- 인증 부분 (JWT 토큰 발급) ---
app.post('/login', (req, res) => {
    const { username, password } = req.body;

    const user = USERS[username];
    if (user && user.password === password) {
        // 인증 성공: JWT 토큰 생성 (페이로드에 사용자 ID와 역할 포함)
        const token = jwt.sign(
            { userId: username, roles: user.roles }, 
            JWT_SECRET, 
            { expiresIn: '1h' } // 토큰 유효 기간 1시간
        );
        return res.json({ message: `Welcome, ${username}!`, token });
    }
    res.status(401).json({ message: "Invalid credentials" });
});

// --- 인가 부분 (JWT 검증 및 역할 기반 미들웨어) ---

// 1. JWT 토큰 검증 미들웨어 (인증)
function authenticateJWT(req, res, next) {
    const authHeader = req.headers.authorization;

    if (authHeader) {
        const token = authHeader.split(' ')[1]; // "Bearer TOKEN" 형식에서 TOKEN 추출

        jwt.verify(token, JWT_SECRET, (err, user) => {
            if (err) {
                // 토큰이 유효하지 않거나 만료됨
                return res.status(403).json({ message: "Forbidden: Invalid or expired token" });
            }
            req.user = user; // 토큰 페이로드 (userId, roles)를 req 객체에 저장
            next(); // 다음 미들웨어 또는 라우터로 이동
        });
    } else {
        res.status(401).json({ message: "Authentication required: No token provided" });
    }
}

// 2. 역할 기반 인가 미들웨어 (인가)
function authorizeRoles(allowedRoles) {
    return (req, res, next) => {
        // authenticateJWT 미들웨어를 통해 req.user에 사용자 정보가 저장되어 있다고 가정
        if (!req.user || !req.user.roles) {
            return res.status(401).json({ message: "Authentication required (no user info)" });
        }

        const userRoles = req.user.roles;
        
        // 사용자가 허용된 역할 중 하나라도 가지고 있는지 확인
        const hasRequiredRole = allowedRoles.some(role => userRoles.includes(role));

        if (hasRequiredRole) {
            next(); // 인가 성공
        } else {
            res.status(403).json({ message: "Authorization denied: Insufficient roles" });
        }
    };
}

// 보호된 라우트
app.get('/admin_panel', authenticateJWT, authorizeRoles(['admin']), (req, res) => {
    res.json({ message: `Welcome to the admin panel, ${req.user.userId}!` });
});

app.get('/user_dashboard', authenticateJWT, authorizeRoles(['user', 'admin']), (req, res) => {
    res.json({ message: `Welcome to your dashboard, ${req.user.userId}!` });
});

app.get('/public_info', (req, res) => {
    res.json({ message: "This is public information, no authentication/authorization needed." });
});

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

설명:

  • /login 엔드포인트는 인증 성공 시 jwt.sign을 사용하여 userIdroles가 포함된 JWT 토큰을 발급합니다.
  • authenticateJWT 미들웨어는 모든 보호된 라우트에 적용되어 HTTP Authorization 헤더에서 JWT를 추출하고 jwt.verify로 토큰의 유효성을 검사합니다. 유효한 경우, 토큰의 페이로드(사용자 정보)를 req.user에 저장합니다. 이것이 인증 과정입니다.
  • authorizeRoles 미들웨어는 authenticateJWT 이후에 실행되며, req.user에 저장된 사용자 roles와 해당 라우트에 필요한 allowedRoles를 비교하여 인가를 수행합니다.
  • /admin_panel은 'admin' 역할만, /user_dashboard는 'user' 또는 'admin' 역할만 접근할 수 있습니다.

4. 실무 적용 사례

  1. 웹 애플리케이션 (프론트엔드/백엔드):
    • 인증: 사용자 로그인 (ID/PW, 소셜 로그인, 생체 인증) 후 세션 또는 JWT 토큰을 발행하여 사용자 신원 유지.
    • 인가: 관리자 페이지, 개인 정보 수정 페이지, 특정 게시물 삭제/수정 버튼 등은 사용자 역할(예: admin, owner) 또는 권한(예: can_delete_post)에 따라 접근을 제한합니다.
  2. API 서버:
    • 인증: 모든 보호된 API 엔드포인트는 요청 헤더에 포함된 API 키, JWT 토큰 또는 OAuth 2.0 액세스 토큰의 유효성을 검사하여 호출자의 신원을 확인합니다.
    • 인가: 특정 API 엔드포인트(예: DELETE /products/{id})는 관리자 권한이 있는 사용자만 호출할 수 있도록 하거나, GET /users/{id}/profile 같은 엔드포인트는 요청하는 사용자가 id에 해당하는 본인이거나 관리자여야만 접근을 허용하도록 합니다.
  3. 마이크로서비스 아키텍처:
    • 인증: API 게이트웨이나 서비스 메시(Service Mesh) 단에서 외부 요청에 대한 인증을 중앙 집중식으로 처리하고, 인증된 사용자 정보를 포함한 토큰을 내부 서비스로 전달합니다.
    • 인가: 각 마이크로서비스는 전달받은 토큰의 사용자 정보를 바탕으로 자신이 제공하는 리소스에 대한 접근 권한을 판단합니다. 서비스 간의 통신에서도 상호 인증(mTLS)과 서비스 계정 기반 인가가 적용될 수 있습니다.
  4. 클라우드 서비스 (AWS IAM, Azure AD 등):
    • 인증: 클라우드 콘솔 로그인, CLI/SDK를 통한 API 호출 시 자격 증명(Access Key, Secret Key)으로 본인임을 인증합니다.
    • 인가: IAM(Identity and Access Management) 역할을 사용하여 특정 사용자나 서비스에게 특정 리소스(S3 버킷, EC2 인스턴스)에 대한 특정 작업(읽기, 쓰기, 삭제) 권한을 부여합니다. 이는 ABAC의 좋은 예시입니다.

5. 자주 하는 실수와 해결법

  1. 인증과 인가를 혼동하거나 분리하지 않음:
    • 실수: "로그인했으니 모든 것을 할 수 있다"고 가정하거나, 인증 로직 안에 인가 로직을 뒤섞는 경우.
    • 해결법: 인증은 '누구인가'를, 인가는 '무엇을 할 수 있는가'를 명확히 분리하여 설계합니다. 인증 시스템은 신원 확인과 토큰 발급에 집중하고, 인가 시스템은 토큰의 사용자 정보를 바탕으로 접근 규칙을 적용하는 데 집중합니다.
  2. 인가 로직 누락 또는 불충분:
    • 실수: 로그인 여부만 확인하고, 해당 사용자가 특정 리소스에 대한 접근 권한이 있는지 상세히 확인하지 않는 경우. 예를 들어, 다른 사용자의 데이터를 URL ID만 바꿔서 접근할 수 있게 되는 경우가 흔합니다 (OWASP Broken Access Control).
    • 해결법: 모든 보호된 리소스와 작업에는 반드시 인가 검사를 수행해야 합니다. 특히, GET 요청이라도 민감한 정보를 다룬다면 인가 검사를 철저히 해야 합니다. **최소 권한의 원칙(Principle of Least Privilege)**을 항상 염두에 두세요.
  3. 클라이언트 측 인가에 의존:
    • 실수: 프론트엔드 코드에서 "관리자 역할이면 이 버튼을 보여주고 아니면 숨겨라"와 같이 UI 제어만으로 인가를 처리하는 경우.
    • 해결법: 클라이언트 측 인가는 UX 개선을 위한 보조 수단일 뿐입니다. 모든 인가 로직은 반드시 서버 측에서 검증되어야 합니다. 클라이언트는 쉽게 조작될 수 있기 때문입니다.
  4. 액세스 토큰 탈취 위험 관리 미흡:
    • 실수: JWT 같은 액세스 토큰을 너무 긴 유효 기간으로 발행하거나, 안전하지 않은 곳에 저장하는 경우.
    • 해결법: 액세스 토큰의 유효 기간을 짧게 설정하고, **리프레시 토큰(Refresh Token)**과 함께 사용하여 보안과 편의성을 동시에 확보합니다. 토큰은 HTTP Only 쿠키나 브라우저의 메모리(세션 스토리지)에 안전하게 저장합니다.
  5. 과도한 권한 부여:
    • 실수: 개발 편의를 위해 사용자에게 필요 이상의 권한을 부여하는 경우.
    • 해결법: 사용자나 서비스에게는 그들이 작업을 수행하는 데 필요한 최소한의 권한만 부여해야 합니다. 이는 보안 침해 시 피해 범위를 최소화하는 데 중요합니다.

6. 더 공부할 리소스 추천

  • OWASP Top 10: 웹 애플리케이션 보안 취약점 목록 중 'Broken Access Control' 항목을 특히 자세히 살펴보세요. 실무에서 인가 관련 실수가 얼마나 치명적인지 알 수 있습니다.
  • JWT (JSON Web Tokens) 공식 문서: 토큰 기반 인증/인가의 핵심 메커니즘인 JWT에 대해 깊이 이해할 수 있습니다.
  • OAuth 2.0 및 OpenID Connect: 이미 다른 게시글에서 다루었지만, 이들은 현대 웹에서 인증과 인가를 구현하는 데 가장 널리 사용되는 표준 프로토콜입니다. 실제 동작 방식을 이해하는 것이 중요합니다.
  • NIST SP 800-162, Role-Based Access Control (RBAC): RBAC의 표준에 대한 상세한 내용을 제공합니다.
  • 서적: "API Security in Action" (닐 메이스터 저): API 보안 전반에 걸쳐 인증, 인가, 토큰 관리 등 실용