UserDetails (4/4)

이전 편: [Spring Security] 3. 로그인 성공·실패 핸들러

이 문서가 시리즈의 마지막 편입니다.

시리즈 안내

이 문서는 로그인 성공·실패 핸들러 이후의 내용입니다.

로그인 후 세션이 어떻게 유지되는지, 그리고 컨트롤러에서 인증 정보를 꺼내는 방법을 다룹니다.


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이 됩니다.