왜 인증을 직접 구현하면 안 되는가
스프링 프로젝트를 처음 만들 때 많은 사람이 이렇게 생각합니다. "로그인이야 뭐, 아이디 비밀번호 맞는지 확인하고 세션에 넣으면 끝 아닌가." 실제로 딱 그 정도까지는 두 시간이면 끝납니다. 문제는 그다음부터입니다.
직접 짠 인증이 무너지는 순서
작은 프로젝트에서 인증을 손으로 구현하면 보통 이런 순서로 기능이 늘어납니다.
- 로그인 성공 시 세션에 사용자 ID를 넣는다.
/admin같은 경로는 관리자만 접근해야 하니 인터셉터를 하나 만든다.- 비밀번호를 평문으로 저장하고 있었다는 걸 뒤늦게 깨닫고 해시 함수를 급하게 붙인다.
- 모바일 앱 클라이언트가 추가되면서 세션 쿠키 대신 토큰이 필요해진다.
- 토큰을 검증하는 로직을 컨트롤러마다 복사해 붙여넣다가, 어느 컨트롤러 하나에 검증을 빼먹는다.
다섯 번째 단계에서 사고가 납니다. 그것도 배포하고 한참 지난 뒤, 로그가 쌓이고 나서야 발견되는 종류의 사고입니다. 인증 로직이 요청 처리 경로 여기저기에 흩어져 있으면, 코드 리뷰어도 그중 하나를 빼먹은 걸 알아채기 어렵습니다. 인증은 "가끔 틀려도 되는" 종류의 로직이 아니라서 이 실패 모드가 유독 아픕니다.
스프링 시큐리티가 하는 일은 사실 대단한 게 아닙니다. 위 다섯 단계에서 반복되는 부분 — 요청을 가로채서 인증 여부를 확인하고, 통과하지 못하면 컨트롤러에 닿기 전에 막는 것 — 을 필터 체인이라는 하나의 표준화된 통로로 강제합니다. 컨트롤러 코드는 인증이 이미 끝났다고 가정하고 짤 수 있게 됩니다.
// 스프링 시큐리티 없이: 컨트롤러마다 이런 코드가 반복된다
@GetMapping("/orders/{id}")
public Order getOrder(HttpSession session, @PathVariable Long id) {
Long userId = (Long) session.getAttribute("userId");
if (userId == null) {
throw new UnauthorizedException();
}
// 여기서 검증을 깜빡하면 그대로 뚫린다
return orderService.findOrder(id, userId);
}// 스프링 시큐리티 적용 후: 컨트롤러는 인증된 사용자만 온다고 가정한다
@GetMapping("/orders/{id}")
public Order getOrder(@AuthenticationPrincipal UserDetails user, @PathVariable Long id) {
return orderService.findOrder(id, user.getUsername());
}두 번째 코드에서 인증 여부를 확인하는 if문이 사라진 게 핵심입니다. 없앤 게 아니라 필터 체인으로 옮긴 겁니다. 그리고 그 필터 체인은 스프링 시큐리티가 수년간 여러 회사의 프로젝트에서 검증한 코드입니다. 제가 개인 프로젝트에서 하루 만에 짠 인터셉터보다 신뢰할 이유가 명백합니다.
그래도 스프링 시큐리티를 미루는 이유는 이해합니다
솔직히 말하면 스프링 시큐리티의 첫인상은 좋지 않습니다. SecurityFilterChain 빈을 등록하자마자 이유도 모른 채 모든 API가 401을 뱉기 시작하고, 공식 문서는 개념을 설명하는 데 시간을 오래 씁니다. 토이 프로젝트 하나 돌리려고 필터 체인 개념부터 배워야 한다는 게 억울하게 느껴질 수 있습니다.
그런데 이 책이 다루는 범위는 딱 그 억울함을 없애는 데 있습니다. 필터 체인이 실제로 어떤 순서로 동작하는지(2장), 세션 기반 인증이 왜 가장 단순한 시작점인지(3장), 비밀번호는 왜 그냥 해시하면 안 되는지(4장), 그리고 실무에서 훨씬 자주 쓰는 JWT 인증을 처음부터 직접 구현하는 것(5장)까지 순서대로 갑니다. 스프링 시큐리티를 "설정 파일에 마법의 문구를 붙여넣는 도구"로 쓰는 게 아니라, 요청 하나가 들어와서 컨트롤러에 닿기까지 무슨 일이 일어나는지 아는 상태로 쓰는 게 목표입니다.
한 가지 미리 밝혀둘 게 있습니다. 이 책은 스프링 부트 3.x 기준으로 작성되었고, 스프링 시큐리티 6 이상을 전제로 합니다. WebSecurityConfigurerAdapter를 상속받는 방식(스프링 시큐리티 5 이하)은 이미 제거되었으므로 다루지 않습니다. 옛날 블로그 글을 검색하다가 configure(HttpSecurity http)를 오버라이드하는 코드를 보셨다면, 그건 지금 버전에서는 컴파일조차 되지 않는다는 것만 기억해두시면 됩니다.