IT Knowledge/Security/images/인증-vs-인가-venn.svg

인증 vs 인가

📌 핵심 차이

쉬운 비유: 회사 출입

  • 인증 (Authentication): “당신이 누구인지 확인” = 사원증 확인
  • 인가 (Authorization): “당신이 무엇을 할 수 있는지 확인” = 보안 등급 확인
┌─────────────────────────────────────────┐
│  회사 정문 (인증)                         │
│  "사원증 보여주세요"                       │
│  → 홍길동 사원 확인 ✓                     │
└─────────────────┬───────────────────────┘
                  │
                  ↓
┌─────────────────────────────────────────┐
│  임원 회의실 (인가)                        │
│  "이 방은 부장급 이상만 들어갑니다"           │
│  → 홍길동은 대리 → 출입 거부 ✗             │
└─────────────────────────────────────────┘

🎯 실전 사례

사례 1: 기본 세션 기반 인증

쉬운 비유: 놀이공원 입장권. 한 번 구매하면(로그인) 팔찌를 받고(세션), 그 팔찌로 계속 인증됩니다.

// 1. 로그인 (인증)
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
 
  // 사용자 확인
  const user = await db.users.findOne({ username });
 
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ error: '잘못된 계정 정보' });
  }
 
  // 세션 생성 (팔찌 지급)
  req.session.userId = user.id;
  req.session.role = user.role;  // 'admin', 'user', 'guest'
 
  res.json({ message: '로그인 성공' });
});
 
// 2. 인증 미들웨어 - "팔찌 있나요?"
function isAuthenticated(req, res, next) {
  if (!req.session.userId) {
    return res.status(401).json({ error: '로그인이 필요합니다' });
  }
  next();
}
 
// 3. 인가 미들웨어 - "부장급 팔찌인가요?"
function isAdmin(req, res, next) {
  if (req.session.role !== 'admin') {
    return res.status(403).json({ error: '권한이 없습니다' });
  }
  next();
}
 
// 사용 예시
app.get('/profile', isAuthenticated, (req, res) => {
  // 로그인만 하면 누구나 볼 수 있음
  const user = db.users.findById(req.session.userId);
  res.json(user);
});
 
app.delete('/users/:id', isAuthenticated, isAdmin, (req, res) => {
  // 관리자만 사용자 삭제 가능
  db.users.deleteById(req.params.id);
  res.json({ message: '삭제 완료' });
});

사례 2: JWT 토큰 기반 인증

쉬운 비유: 비행기 탑승권. 탑승권에 이름, 좌석 등급(일반/비즈니스), 유효기간이 인쇄되어 있어 직원이 즉시 확인 가능.

const jwt = require('jsonwebtoken');
 
// 1. 로그인 시 JWT 발급
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
 
  const user = await db.users.findOne({ username });
 
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ error: '잘못된 계정 정보' });
  }
 
  // JWT 토큰 생성 (탑승권 발급)
  const token = jwt.sign(
    {
      userId: user.id,
      username: user.username,
      role: user.role  // 권한 정보 포함
    },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }  // 유효기간 24시간
  );
 
  res.json({ token });
});
 
// 2. 요청 시 토큰 검증
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];  // "Bearer TOKEN"
 
  if (!token) {
    return res.status(401).json({ error: '토큰이 필요합니다' });
  }
 
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: '유효하지 않은 토큰' });
    }
 
    req.user = user;  // 토큰에서 사용자 정보 추출
    next();
  });
}
 
// 3. 역할 기반 인가 (RBAC)
function authorize(...allowedRoles) {
  return (req, res, next) => {
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ error: '권한이 없습니다' });
    }
    next();
  };
}
 
// 사용 예시
app.get('/products', (req, res) => {
  // 누구나 상품 목록 조회 가능 (인증 불필요)
  res.json(products);
});
 
app.post('/products', authenticateToken, authorize('admin', 'editor'), (req, res) => {
  // 관리자 또는 편집자만 상품 추가 가능
  const product = req.body;
  db.products.insert(product);
  res.json({ message: '상품 추가 완료' });
});
 
app.delete('/products/:id', authenticateToken, authorize('admin'), (req, res) => {
  // 관리자만 상품 삭제 가능
  db.products.deleteById(req.params.id);
  res.json({ message: '삭제 완료' });
});

사례 3: OAuth 2.0 소셜 로그인

쉬운 비유: 운전면허증으로 신분증 대신 사용. 경찰청(Google)이 “이 사람 신원 확인됨”이라고 보증해주는 것.

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
 
// Google OAuth 설정
passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: 'http://localhost:3000/auth/google/callback'
  },
  async (accessToken, refreshToken, profile, done) => {
    // Google에서 받은 프로필 정보
    const email = profile.emails[0].value;
 
    // 우리 DB에서 사용자 찾기 또는 생성
    let user = await db.users.findOne({ email });
 
    if (!user) {
      user = await db.users.insert({
        email,
        name: profile.displayName,
        googleId: profile.id,
        role: 'user'  // 기본 역할
      });
    }
 
    return done(null, user);
  }
));
 
// Google 로그인 시작
app.get('/auth/google',
  passport.authenticate('google', {
    scope: ['profile', 'email']
  })
);
 
// Google 로그인 콜백
app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    // 로그인 성공
    const token = jwt.sign(
      { userId: req.user.id, role: req.user.role },
      process.env.JWT_SECRET
    );
 
    res.redirect(`/dashboard?token=${token}`);
  }
);

사례 4: 리소스 기반 인가 (ABAC)

쉬운 비유: 자기 집은 들어갈 수 있지만 남의 집은 못 들어감. 역할이 아닌 “소유권”으로 판단.

// 게시글 소유자만 수정 가능
app.put('/posts/:id', authenticateToken, async (req, res) => {
  const postId = req.params.id;
  const userId = req.user.userId;
 
  // 게시글 조회
  const post = await db.posts.findById(postId);
 
  if (!post) {
    return res.status(404).json({ error: '게시글이 없습니다' });
  }
 
  // 소유권 확인
  if (post.authorId !== userId && req.user.role !== 'admin') {
    return res.status(403).json({
      error: '본인 게시글만 수정할 수 있습니다'
    });
  }
 
  // 수정 진행
  await db.posts.update(postId, req.body);
  res.json({ message: '수정 완료' });
});
 
// 팀 멤버만 프로젝트 조회 가능
app.get('/projects/:id', authenticateToken, async (req, res) => {
  const projectId = req.params.id;
  const userId = req.user.userId;
 
  const project = await db.projects.findById(projectId);
 
  // 팀 멤버인지 확인
  const isMember = project.members.includes(userId);
 
  if (!isMember && req.user.role !== 'admin') {
    return res.status(403).json({ error: '프로젝트 멤버가 아닙니다' });
  }
 
  res.json(project);
});

💡 인증 전략 비교

방식장점단점사용 사례
세션서버에서 제어 가능, 즉시 로그아웃확장성 낮음, 메모리 사용전통적인 웹앱
JWT확장성 좋음, 상태 없음로그아웃 어려움, 토큰 크기마이크로서비스, API
OAuth사용자 편의, 책임 분산외부 의존성SNS 로그인

🔗 참고 자료