1. 세션 기반 인증 흐름
로그인에 성공하면 Spring Security는 자동으로 세션을 생성하고 JSESSIONID 쿠키를 클라이언트에 발급합니다.
sequenceDiagram
participant Client
participant Filter as Security Filter
participant Controller
Client->>Filter: POST /api/auth/login (username, password)
Filter->>Filter: 인증 성공
Filter-->>Client: 200 OK + Set-Cookie: JSESSIONID=abc123
Client->>Filter: GET /api/auth/me (Cookie: JSESSIONID=abc123)
Filter->>Filter: 세션에서 인증 정보 복원
Filter->>Controller: 인증된 요청 전달
Controller-->>Client: 200 OK + UserDto
- 클라이언트는 이후 모든 요청에
JSESSIONID쿠키를 자동으로 포함합니다 - Security Filter가
JSESSIONID로 세션을 찾아SecurityContext를 복원합니다 - 인증된 상태이므로 컨트롤러까지 요청이 도달합니다
2. SecurityContext란?
SecurityContext 는 현재 인증된 사용자 정보를 담고 있는 컨테이너입니다.
SecurityContextHolder
└── SecurityContext
└── Authentication
└── Principal (DiscodeitUserDetails)
SecurityContextHolder: 현재 스레드의 SecurityContext를 관리Authentication: 인증된 사용자 정보 + 권한 목록Principal:loadUserByUsername()이 반환한UserDetails구현체
3. 컨트롤러에서 인증 정보 꺼내기
방법 1. @AuthenticationPrincipal (권장)
@GetMapping("/me")
public UserDto getMe(@AuthenticationPrincipal DiscodeitUserDetails userDetails) {
return userDetails.getUserDto();
}
@AuthenticationPrincipal: SecurityContext에서Principal을 자동으로 주입- 간결하고 테스트하기 쉬운 현대적인 방식
방법 2. SecurityContextHolder 직접 사용
@GetMapping("/me")
public UserDto getMe() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
DiscodeitUserDetails userDetails = (DiscodeitUserDetails) authentication.getPrincipal();
return userDetails.getUserDto();
}
- 컨트롤러 외부(서비스 레이어 등)에서 인증 정보가 필요할 때 사용
- 파라미터로 받을 수 없는 상황에서 유용
어디서든 현재 로그인한 사용자를 알고 싶다면?
서비스 레이어에서도 SecurityContextHolder.getContext().getAuthentication()으로 꺼낼 수 있습니다.
4. 구현 예시
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@GetMapping("/me")
public UserDto getMe(@AuthenticationPrincipal DiscodeitUserDetails userDetails) {
return userDetails.getUserDto();
}
}
- 별도 서비스 호출 없이 세션에서 바로 꺼내 반환합니다
JSESSIONID가 유효하지 않거나 없으면 Security Filter가 401을 반환합니다
5. 자주 하는 실수
[실수] 미인증 상태에서 @AuthenticationPrincipal이 null
permitAll()로 열린 엔드포인트에서 @AuthenticationPrincipal을 쓰면 로그인하지 않은 경우 null이 주입됩니다. 이 경우 NullPointerException이 발생하므로 null 체크가 필요합니다.
[!WARNING] [실수] SecurityConfig에서 /api/auth/me를 permitAll()로 열지 말 것
/me는 인증된 사용자만 접근해야 합니다. permitAll()로 열면 @AuthenticationPrincipal이 null이 됩니다.