
UsernamePasswordAuthenticationFilter, ExceptionTranslationFilter, etc.)DelegatingFilterProxy를 사용FilterChainProxy에 위임함[톰캣 필터 체인]
↓
[DelegatingFilterProxy (서블릿 필터)]
↓
[FilterChainProxy (Spring Security 필터 체인)]
↓
[DispatcherServlet]
↓
[HandlerMapping → Controller 호출]

1. 로그인(인증 성공) 시
SecurityContextHolder.getContext().setAuthentication(authentication);
HttpSession.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
→ 이로써 "세션이 존재하고 인증 정보도 포함"된 상태가 된다.
2. 다음 요청이 들어왔을 때

SecurityContext context = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
SecurityContextHolder.setContext(context);
서버 응답 헤더
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=ABC123XYZ456; Path=/; HttpOnly
→ 브라우저는 이 헤더를 보고 JSESSIONID=ABC123XYZ456 쿠키를 저장
이후 브라우저의 요청
GET /me HTTP/1.1
Host: example.com
Cookie: JSESSIONID=ABC123XYZ456
→ 브라우저가 같은 도메인, 같은 Path일 경우 자동으로 쿠키를 포함해서 보냄
로그인 방식에 따라 UsernamePasswordAuthenticationFilter 또는 커스텀 필터(JsonUsernamePasswordAuthenticationFilter 등)가 로그인 요청을 처리한다.
로그인 요청 본문을 파싱하여 LoginRequest 객체로 만든 후,
이를 기반으로 UsernamePasswordAuthenticationToken을 생성하여
AuthenticationManager.authenticate()에 전달한다.
AuthenticationManager는 등록된 AuthenticationProvider를 통해
실제 인증 처리를 위임한다.
AuthenticationProvider는 내부적으로 UserDetailsService.loadUserByUsername()를 호출하여
해당 사용자의 정보를 조회하고,
PasswordEncoder.matches(raw, encoded)로 비밀번호 검증을 수행한다.
인증에 성공하면, Authentication 객체를 반환하며,
이 객체 안의 principal은 UserDetails 구현체이고,
credentials(비밀번호)는 보안상 null로 처리되어 반환된다.
반환된 Authentication은 SecurityContextHolder.getContext().setAuthentication(...)를 통해
SecurityContext에 저장된다.
@Service
@RequiredArgsConstructor
// Spring Security의 인증 과정에서 사용자 정보를 로딩하기 위한 핵심 인터페이스이고,
// DaoAuthenticationProvider가 이걸 이용해서 사용자(username)를 조회
// 비밀번호 검증은 PasswordEncoder 사용
public class BlogUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private final UserMapper userMapper;
@Transactional(readOnly = true)
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User with username " + username + " not found"));
return new BlogUserDetails(userMapper.toDto(user), user.getPassword());
}
}
// SecurityConfig, 굳이 안해줘도 UserDetailsService와 PasswordEncoder가 Bean등록 되어있다면 자동 Autowired됨, 하지만 역할 계층을 넣어주려면 이렇게 생성해줘야한다!!
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
RoleHierarchy roleHierarchy
) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
// 등록해줘야만 Spring Security에서 역할 계층(Role Hierarchy)이 실제로 적용됨
// provider에서 인증 후 UserDetailsService로부터 조회한 UserDetails.getAuthorities()를 AuthoritiesMapper를 통해 가공한 뒤,
// 이 권한 목록을 Authentication 객체의 GrantedAuthorities에 세팅
provider.setAuthoritiesMapper(new RoleHierarchyAuthoritiesMapper(roleHierarchy));
return provider;
}
public ResponseEntity<UpdateReadStatusResponseDTO> updateReadStatus(
Authentication authentication) {
UUID id = ((CustomUserDetails)authentication.getPrincipal()).getUserDto().id());
}
public ResponseEntity<UpdateReadStatusResponseDTO> updateReadStatus(
@AuthenticationPrincipal CustomUserDetails principal) {
UUID id = principal.getUserDto().id());
}
UUID id = ((CustomUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserDto().id()