1. 세션 기반 인증(Session-Based Authentication)
- 웹 보안에서 가장 전통적이고 널리 쓰이는 로그인 방식
1️⃣ 세션 인증
1) 세션 인증이란?
“로그인 상태를 서버가 기억하는 방식”
사용자가 로그인하면 서버가 “이 사용자는 인증된 사용자다”라고 기억(저장)
➡️ 그 기억을 식별하기 위해 사용하는 것 세션 ID (Session ID)
2) 세션 인증의 핵심 개념
세션 인증을 이해하려면 3가지만 알면 됩니다:
요소 | 역할 |
사용자 (브라우저) | 로그인 요청을 보냄 |
서버 | 로그인 성공 여부 판단 & 상태 기억 |
세션 ID | 로그인 상태를 식별하는 표식 |
3) 동작 원리 (큰 그림)
로그인 성공 → 서버가 세션 생성 → 세션 ID 발급 → 브라우저가 저장 → 이후 요청마다 제출
2️⃣ 실행 흐름 (단계별)
0) 예시 시나리오
POST /login: 아이디/비밀번호 로그인
GET /me: 로그인한 사용자 정보(보호 API)
POST /logout: 로그아웃
➡️ 세션에 저장할 값 :
LOGIN_USER 라는 키로 userId, roles 등 저장1) 로그인 요청 → 컨트롤러에서 아이디/비밀번호 받기
- 실행 흐름
사용자가 아이디 / 비밀번호 입력 → 서버로 전송- 코드
// LoginRequest.java
public record LoginRequest(String username, String password) {}// AuthController.java
@RestController
public class AuthController {
private final UserService userService;
public AuthController(UserService userService) {
this.userService = userService;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req,
HttpServletRequest request) {
// 2) 서버가 인증 확인 (아래 단계에서 계속)
User user = userService.authenticate(req.username(), req.password());
// 3) 서버가 세션 생성 + 사용자 정보 연결
HttpSession session = request.getSession(true); // 없으면 새로 생성
session.setAttribute("LOGIN_USER", new SessionUser(user.id(), user.username(), user.roles()));
// 선택: 세션 타임아웃(초) 설정 (예: 30분)
session.setMaxInactiveInterval(30 * 60);
// 4) 세션 ID 쿠키는 보통 자동으로 내려감(JSESSIONID)
return ResponseEntity.ok().build();
}
}➡️ 여기서
request.getSession(true) 호출 순간이 **“서버가 세션 생성”**에 해당이후 응답을 보낼 때 컨테이너(톰캣)가
Set-Cookie: JSESSIONID=... 를 자동으로 내려줌2) 서버가 인증 확인 → 비밀번호 검증 로직(서비스)
- 실행 흐름
비밀번호 맞음 → 로그인 성공
- 코드
// UserService.java
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; // BCrypt 권장
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public User authenticate(String username, String rawPassword) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UnauthorizedException("Invalid credentials"));
if (!passwordEncoder.matches(rawPassword, user.passwordHash())) {
throw new UnauthorizedException("Invalid credentials");
}
return user;
}
}➡️ 비밀번호는 반드시 해시(BCrypt 등)로 저장하고
matches로 비교3) 서버가 세션 생성
- 서버 내부
세션 저장소 생성
세션 ID 생성 (예: XH23KJASD89)
사용자 정보 연결- 개념
세션 ID → 사용자 정보 매핑
- 예
XH23KJASD89 → 홍길동 사용자- 코드 (세션에 저장할 사용자 모델 정의)
// SessionUser.java
public record SessionUser(Long userId, String username, List<String> roles) {}4) 세션 ID를 브라우저에 전달
- 서버 → 브라우저
쿠키(Cookie)에 세션 ID 저장 (자동 저장)
5) 이후 모든 요청에서 세션 검사 → Filter(또는 Interceptor)
- 보호가 필요한 경로(
/me,/api/**등)에 대해 요청마다 - 요청 쿠키의 JSESSIONID로 세션이 잡힘(컨테이너가 해줌)
- 우리는
session.getAttribute("LOGIN_USER")가 있는지 확인
- 서블릿 필터 코드
// SessionAuthFilter.java
@Component
public class SessionAuthFilter implements Filter {
private static final Set<String> PUBLIC_PATHS = Set.of(
"/login", "/health"
);
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String path = request.getRequestURI();
if (isPublic(path)) {
chain.doFilter(req, res);
return;
}
HttpSession session = request.getSession(false); // 없으면 만들지 않음
SessionUser loginUser = (session == null) ? null : (SessionUser) session.getAttribute("LOGIN_USER");
if (loginUser == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 필요하면 request attribute로 내려서 컨트롤러에서 쉽게 사용
request.setAttribute("LOGIN_USER", loginUser);
chain.doFilter(req, res);
}
private boolean isPublic(String path) {
if (PUBLIC_PATHS.contains(path)) return true;
return false;
}
}핵심 차이
getSession(true): 세션 만들기
getSession(false): 세션 없으면 그냥 null (보호 요청에서 이게 중요)
6) 보호 API에서 “로그인 사용자” 사용하기
// UserController.java
@RestController
public class UserController {
@GetMapping("/me")
public ResponseEntity<?> me(HttpServletRequest request) {
SessionUser user = (SessionUser) request.getAttribute("LOGIN_USER");
return ResponseEntity.ok(user);
}
}- 컨트롤러에서
HttpSession직접 꺼내도 됨
7) 로그아웃(세션 무효화)
// AuthController.java 안에 추가
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate(); // 세션 폐기
}
return ResponseEntity.ok().build();
}invalidate()가 서버에서 즉시 끊기를 가능하게 하는 게 세션 방식의 강점
8) “세션 ID 쿠키” 보안 설정은 어디서 하냐?
- 세션 쿠키(JSESSIONID)는 대개 자동이지만, 보안 속성은 설정해야 함
- application.yml (톰캣/스프링 부트 기본 세션 쿠키 설정)
server:
servlet:
session:
timeout: 30m
cookie:
http-only: true
secure: true # HTTPS에서만 전송
same-site: lax # 크로스 사이트 정책에 따라 Lax/Strict/NoneHttpOnly=true: JS로 쿠키 접근 제한(XSS 완화)Secure=true: HTTPS에서만 전송SameSite: CSRF/크로스사이트 요청 완화(단, 요구사항 따라 조정)9) (중요) “직접 구현 세션 인증”에서 자주 빠지는 보안 포인트
- CSRF: 브라우저 쿠키 기반이면 기본적으로 CSRF 위험이 생깁니다. (특히 state-changing 요청)
→ 최소한
SameSite, 가능하면 CSRF 토큰 도입 고려- 세션 고정(Session Fixation): 로그인 전 세션을 로그인 후에도 그대로 쓰면 위험할 수 있음
→ 로그인 성공 시
session.invalidate() 후 새 세션 발급 같은 패턴 고려(스프링 시큐리티는 기본 방어를 제공합니다)- 세션 만료/재발급: 민감한 작업(비번 변경 등) 후 세션 재발급 고려
10) 결론: “단계별 실행흐름 ↔ 코드” 매핑 한 줄 요약
- (1) 로그인 요청:
@PostMapping("/login")에서 자격 증명 수신
- (2) 인증 확인:
passwordEncoder.matches()등으로 검증
- (3) 세션 생성:
request.getSession(true)
- (4) 세션 ID 발급/쿠키 저장: 톰캣이 자동 처리(JSESSIONID Set-Cookie)
- (5) 이후 요청 인증: Filter/Interceptor에서
getSession(false)+getAttribute("LOGIN_USER")
3️⃣ 비유로 이해하기 (가장 쉬운 설명)
세션 인증 = 헬스장 출입 팔찌 시스템
1) 등록할 때
- 신분증 확인 → 팔찌 지급
2) 이후 출입할 때
- 팔찌만 보여주면 됨
➡️ 팔찌 = 세션 ID
➡️ 헬스장 DB = 서버 세션 저장소
4️⃣ 왜 세션 인증이 안전한가?
중요 포인트 👇
✔ 비밀번호는 매번 보내지 않음
✔ 서버가 상태를 직접 관리
✔ 민감 정보는 서버 내부에만 존재
5️⃣ 세션 인증의 단점
1) ❌ 서버 메모리 사용
- 사용자마다 세션 저장 필요
사용자 많음 → 서버 부담 증가
2) ❌ 확장성 문제
- 서버 여러 대일 경우
세션 공유 필요 (Redis 등 사용)
3) ❌ 세션 탈취 위험
- 공격자가 세션 ID 획득 시
로그인 없이 사용자 행세 가능
- 그래서 사용하는 보안 기술 👇
- HTTPS
- HttpOnly 쿠키
- Secure 쿠키
- 세션 만료 시간
6️⃣ 세션 vs 토큰 인증 간단 비교
구분 | 세션 인증 | 토큰 인증 (JWT 등) |
상태 저장 | 서버 | 클라이언트 |
서버 부담 | 큼 | 적음 |
확장성 | 불리 | 유리 |
전통 웹 | 많이 사용 | 현대 API / 모바일 |
Share article