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 로그인 |