Spring Security 정리
새로운 프로젝트를 들어가면서 다시 한 번 제대로 정리하고 가야겠다.
먼저 Spring 백엔드를 만들면서 초기에 해야하는 단계인 Security 부분을 살펴보겠다.
개발자가 Security를 구성한다면 해야할 순서이다.
1. SecurityConfig 설정 (기본 필터 체인 설정)
2. JWT 인증 필터 만들기
3. 사용자 정보 조회 서비스 구현(UserDetailsService)
4. 비밀번호 인코딩 설정
5. 로그인 & 회원가입 API 구현
6. 인증 예외 처리
Security 중요 객체들
Authentication (인증 객체)란?
현재 사용자가 누구인지, 인증이 되었는지, 어떤 권한을 가지고 있는지에 대한 정보를 담고 있는 객체
사용자 인증 정보 덩어리
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
인증 객체는
SecurityContextHolder → SecurityContext → Authentication
이렇 구조로 저장됨
SecurityContextHolder
└── SecurityContext
└── Authentication
├── Principal (사용자 정보)
├── Credentials (비밀번호 등)
└── Authorities (권한)
이렇게 나누는게 스프링의 중요한 기능인 관심사 분리
계층 | 역할 |
SecurityContextHolder | 어디서든 꺼낼 수 있는 글로벌 저장소 (ThreadLocal 기반) |
SecurityContext | "현재 요청의 인증 상태"를 담는 객체 |
Authentication | 로그인한 사용자에 대한 전체 정보 |
Principal | 진짜 사용자 정보 (예: UserDetails, User) |
인증객체에는 다양한 구현체가 있을 수 있고, 이걸 다 담는게 SecurityContext
UsernamePasswordAuthenticationToken: ID/PW 기반 인증 객체
OAuth2AuthenticationToken: OAuth2 기반 인증 객체
SecurityContextHolder란?
현재 인증된 사용자의 정보를 보관하는 전역 저장소
즉, 로그인한 사용자가 누구인지, 인증되었는지, 어떤 권한이 있는지를 여기에 저장해둠
스프링 시큐리티는 HTTP 요청마다 인증 정보를 요청 스레드에 저장해 놓고,
그 요청 처리 중 어디서든 꺼내 쓸 수 있게 전역 저장소 역할을 하는 것이다.
ThreadLocal에 저장되는데 이건 현재 실행 중인 스레드마다 고유한 데이터를 저장하고 읽을 수 있는 자바 클래스임
이것 때문의 멀티 쓰레드가 가능한 것임
이 스레드의 생명주기를 보자면
스레드의 생명주기
생성 시점
HTTP 요청이 들어오고, Spring Security 필터 체인이 시작될 때
- 사용자가 API 요청을 보냄
- SercurityContextPersistenceFilter 라는 필터가 작동
- 이 필터가 SecurityContextHolder.setContext() 호출
- 내부적으로 ThreadLocal<SecurityContext>가 만들어지고 저장됨
제거 시점
HTTP 응답이 끝나고, 요청 처리가 완료될 때
- 다시 SercurityContextPersistenceFilter 가 마지막에 동작해서
- SecurityContextHolder.clearContext();
- ThreadLocal.remove() 호출됨
- 현재 스레드에 저장된 인증 정보 제거됨
SecurityContext란?
현재 사용자의 Authentication(인증 정보)를 담고 있는 객체
이건 한 명의 사용자(한 요청)의 인증 정보를 담고 있음
내부에 Authentication 객체 1개를 갖고 있고,
이 객체 안에 사용자의 정보 (Pricipal, Credentials, Authorities 등)들이 들어 있음
SecurityContextHolder (보관함)
└── SecurityContext (문서철)
└── Authentication (문서)
├── Principal (사용자)
├── Credentials (비밀번호 등)
└── Authorities (권한)
만약 이 인증 정보가 필요하다면
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
이렇게 전역저장소에서 꺼내서 사용하면 되는 것이다.
AuthenticationManager
요청이 오면 적절한 AuthenticationProvider에게 일을 맡겨서 인증을 처리해주는 중간 관리자
Authentication auth = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(email, password)
);
// 이걸 SecurityConfig에 넣으면 AM이 빈으로 등록됨
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
- 이런 식으로 인증 객체(이메일과 비번 같은 거)를 받음
- 등록된 AuthenticationProvider 중에서 이 요청을 처리할 수 있는 걸 찾음
- 해당 Provider(DaoAuthenticationProvider)가 DB에서 사용자 조회 + 비번 비교
- 성공 시: 인증된 Authentication 객체 리턴
- 실패 시: 예외 던짐
UsernamePasswordAuthenticationToken
이건 User가 누구인지를 담은 Principal
어떤게 인증할 건지를 담은 Credentials(비밀번호)
인증이 끝나면 이 객체에 Credentias은 지워지고 null
AuthenticationProvider란?
인증을 모듈화된 구조로 처리하는데 그 핵심 인터페이스다.
로그인 시 주어진 인증 정보가 유효한지 판단하고, 인증 객체를 반환하는 역할
DaoAuthenticationProvider
이게 대표적인 구현체 중 하나이다.
DB를 통해 사용자 정보를 조회하고, 비밀번호를 비교해서 인증을 처리해주는 Provider
내부적으로
- UserDetailsService
- DB에서 사용자 정보 조회
- PasswordEncoder
- 평문 비밀번호와 암호화된 비밀번호 비교
- supports(): 어떤 인증 타입을 지원하는지 판단
- authenticate(): 진짜 인증 처리 로직
SecurityFilterChain란?
Security 필터들을 등록하는 체인이다.
요청을 가로채 등록된 필터들을 통과하게 한다.
여기에 JwtAuthenticationFilter를 등록하게 하는 것이다.
JwtAuthenticationFilter란?
SecurityFilterChain에 포함되어 요청에 JWT가 있는지, 있다면 유효한지, 유효하다면 인증객체를 만들어 주는 로직이 포함되어 있다.
이 필터 클래스는 OncePerRequestFilter를 상속받는다. -> 하나의 요청에 한 번만 실행될 수 있게 하는 베이스 클래스
입력 받은 로그인 정보로 유저정보 얻고 다시 UserDetails를 만들어 인증 객체를 또 만듬
UserDetails 란?
Spring Security가 내부적으로 인증된 사용자 정보를 담기 위해 사용하는 인터페이스
UserDetailsService를 통해 이 객체를 만든다.
UserDetailsService란?
UserDetails를 만들기 위한 Service객체
loadUserByUsername을 구현해야한다.
이 메서드는 JWT 속 유저 정보를 통해 UserDetails 만들어 리턴하는 역할이다.
마무리
이번에 알아본건 JWT 기반의 인증 처리였다.
그 과정에서 정말 다양한 객체들이 등장하고 모두 중요하게 사용된다.
이걸 하나하나 머리속에 넣지 않으면 동작 이해가 안될 거 같다.
이렇게 구성한 바탕에서 OAuth2를 추가해야하는게 내 역할이다.
다음에는 OAuth2가 뭔지와 어떻게 Spring Security를 통해 구현하는지 알아보겠다.