인증과 인가
- 인증(Authentication) : 사용자가 누구인지 검증하는 과정
ex) 로그인
- 인가(Authorization) : 사용자의 권한에 따라 허가를 내주는 것
ex) 블로그 글쓰기, 글 수정, 삭제하기
http 통신은 stateless하다. 이전에 전달받은 요청이 어느 클라이언트로부터 왔는지 기억을 하지 못한다.
이 사람과 이전에도 통신을 한 적 있는지, 어떤 데이터를 주고 받았는지 모르는 것이다.
그렇다고 매 요청마다 클라이언트가 서버에게 내가 누구인지 검증할 수는 없을 것이다.
이를 해결하기 위해 사용되는 것이 쿠키, 세션, 토큰 인증 방식이다.
덕분에 로그인을 한 번 하면 몇 시간동안 로그인을 하지 않아도 되고, 사이트 팝업을 하루동안 보지 않도록 설정할 수도 있고, 동일 사이트를 재방문시 id와 pw의 정보가 저장되어 있는 등 이전 통신에서의 정보를 기억해둘 수 있다.
쿠키 인증
서버가 클라이언트 측에 제공하는 작은 데이터 조각이다.
클라이언트가 처음 요청을 보내고 서버가 이에 응답할 때 '쿠키'를 함께 전송한다.
- 응답 헤더의 Set-Cookie에 저장할 정보를 담아 보낸다.
클라이언트 측은 이를 브라우저에 저장해 다음 요청부터 쿠키를 함께 첨부한다.
- 요청 헤더의 Set-Cookie에 담아 보낸다.
👍
사용자 pc에 저장되므로 빠르고 서버의 자원을 낭비하지 않는다.
👎
- 보안에 취약하다.
요청할 때마다 쿠키를 함께 보내기 때문에 중간에 유출될 위험이 크다.
사용자의 pc에 저장되기 때문에 해킹의 위험성도 크다.
이때문에 민감한 정보는 담지않도록 한다.
- 쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없다.
세션 인증
쿠키 인증 방식은 위 설명과 같이 보안에 취약하다.
세션Id는 서버 측에 저장되기 때문에 비밀번호같은 민감한 정보는 세션 인증 방식을 자주 이용한다.
사용자의 로그인 상태 유지에 대표적으로 쓰이는데,
서버에서 세션을 생성하고, 클라이언트에는 해당 섹션을 식별할 수 있는 고유한 ID를 쿠키로 저장하는 방식이다.
특징
- 서버에서 관리: 중요한 로그인 정보는 세션에 저장된다.
- 클라이언트는 세션 ID만 저장한다:
중요한 정보는 서버에 있고, 브라우저에는 단순한 세션 ID만 쿠키로 저장한다.
이 세션 ID는 세션 정보를 식별하는 역할을 한다.
- 로그아웃 시 세션과 세션 ID 모두 삭제 및 만료되기 때문에 해킹에 비교적 안전
👍
- 보안성 :
중요한 정보는 서버에 저장되기 때문에 보안성을 높일 수 있음
로그아웃 시 세션과 세션 ID 모두 삭제 및 만료되기 때문에 해킹에 비교적 안전
👎
- 서버 부하 증가:
많은 사용자가 접속하면 서버 메모리 사용량이 증가할 수 있음
- 로드 밸런싱 이슈:
여러 개의 서버를 사용하는 경우, 세션을 공유하는 설정이 필요
- 쿠키 탈취 위험:
세션 ID가 탈취되면 다른 사용자의 세션을 도용할 수 있음
-> HttpOnly, Secure, SameSite 등의 옵션 설정 가능
예시 코드
(server.js)
const express = require('express');
const session = require('express-session');
const cors = require('cors');
const app = express();
app.use(express.json());
// CORS 설정 (쿠키 전송을 위해 credentials 옵션 활성화)
app.use(cors({
origin: 'http://localhost:3000', // 프론트엔드 주소
credentials: true
}));
// 세션 설정
app.use(session({
secret: 'mySecretKey', // 세션 암호화 키
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // JavaScript에서 쿠키 접근 방지
secure: false, // HTTPS에서만 작동 (개발 환경에서는 false)
maxAge: 1000 * 60 * 30 // 30분 후 세션 만료
}
}));
// 가짜 유저 데이터
const users = [{ username: 'admin', password: '1234' }];
// 로그인 API
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (user) {
req.session.user = { username }; // 세션 저장
res.json({ success: true, message: '로그인 성공!' });
} else {
res.status(401).json({ success: false, message: '아이디 또는 비밀번호가 틀립니다.' });
}
});
// 로그인 상태 확인 API
app.get('/profile', (req, res) => {
if (req.session.user) {
res.json({ loggedIn: true, user: req.session.user });
} else {
res.json({ loggedIn: false });
}
});
// 로그아웃 API
app.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) return res.status(500).json({ success: false, message: '로그아웃 실패' });
res.clearCookie('connect.sid'); // 세션 쿠키 삭제
res.json({ success: true, message: '로그아웃 성공' });
});
});
app.listen(5000, () => console.log('서버 실행 중! 🚀 http://localhost:5000'));
(front/src/api.js)
export const login = async (username, password) => {
const res = await fetch('http://localhost:5000/login', {
method: 'POST',
credentials: 'include', // 쿠키 포함
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
return res.json();
};
export const checkProfile = async () => {
const res = await fetch('http://localhost:5000/profile', {
method: 'GET',
credentials: 'include' // 쿠키 포함
});
return res.json();
};
export const logout = async () => {
const res = await fetch('http://localhost:5000/logout', {
method: 'POST',
credentials: 'include' // 쿠키 포함
});
return res.json();
};
토큰 인증
서버에서 해당 클라이언트에게 인증되었다는 의미로 토큰을 부여하고,
이후 요청마다 이 토큰을 검증하여 사용자를 인증하는 방식.
대표적으로 JWT 방식이 있다.
JWT(JSON Web Token)
서버가 발급한 인증 토큰으로, 클라이언트는 JWT를 요청 헤더에 포함시켜 서버에 보냄으로써 본인 인증이 가능하다.
JWT 구조
: 헤더(Header)/페이로드(Payload)/서명(Signature)
Header: 알고리즘& 토큰 타입
{
"alg": "HS256",
"typ": "JWT"
}
Payload: 데이터 정보
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
내용을 전달하는 데이터를 포함한다.
단순 Base64 인코딩된 파트로, 이는 누구나 디코딩해 데이터 열람이 가능하다.
때문에 pw같은 민감한 정보는 담기면 안된다.
Signature: 검증
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
서명 파트는 Base64 인코딩된 Header"."Payload 그리고 secret이 필요하다.
-> Header + Payload를 secret key로 암호화한 값
token의 유효여부는 JWT 라이브러리의 복호화 로직을 사용해 서명의 secret을 확인한다.
✅ 검증을 위해 DB 조회가 필요한가?
JWT는 기본적으로 "토큰만으로 사용자 인증이 가능"한 방식이다.
JWT는 클라이언트가 서버로부터 받은 토큰에 필요한 정보를 모두 포함하고 있어서,
별도의 DB 조회 없이도 사용자 인증이 가능하다.
서명이 올바르면 DB 조회 필요 없이, JWT 안에 포함된 정보(id, role, name, exp 등)를 그대로 사용 가능
서버에 저장된 secret key의 일치여부, 만료 여부, 유저 정보, 권한 등을 따지면 된다.
+) 추가 사항
access token과 refresh token
대칭키와 비대칭키
'Web' 카테고리의 다른 글
| HTTP 캐싱과 캐싱 제어 (0) | 2025.03.06 |
|---|---|
| 리플로우(Reflow)와 리페인트(Repaint) & 리플로우 최적화 (0) | 2025.02.26 |
| 브라우저 렌더링 작동 원리: 파싱부터 렌더링까지 (0) | 2025.02.26 |
| "웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가" (0) | 2025.02.13 |
| CSR과 SCR, SPA와 MPA (0) | 2025.01.21 |