스프링 시큐리티 인증 원리(로그인 할때)

1. usernamePasswordAuthenticationToken에 username과 password를 저장
2. authenticationManager에 usernamePasswordAuthenticationToken을 전달
3. authenticationManager의 내부 동작으로 userDetailsService와 passwordEncoder를 호출하여 DB조회를 통한 이메일 확인, 비밀번호 검증을한다.
=> 이때 사용되는것이 userDetailsService의 loadUserByUsername
코드로 작성하면 다음과 같음
public LoginResDto login(LoginReqDto loginReqDto) {
// 인증 시도 ( authenticationManager 추후 수정 예정)
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginReqDto.getEmail(),
loginReqDto.getPassword()
)
);
// 유저 정보 가져오기
CustomUserDetails userDetails = (CustomUserDetails)authentication.getPrincipal();
// JWT 토큰 생성
String jwt = jwtTokenProvider.createToken(userDetails);
return new LoginResDto(jwt);
}
JwtFilter (로그인 이후)
로그인 이후부터는 jwt를 사용하여 http 요청이 들어올 때마다 인증한다.
-> 매번 db조회를 하지 않는다 / jwt 토큰으로 이미 인증 되었음을 보장하기때문에
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String jwt = resolveToken(request);
if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
Authentication authentication = jwtTokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
1. 토큰을 가지고 있지 않다면 인증을 진행하지 않고 다음 필터를 실행하도록 요청한다 => 로그인 전 or 토큰 없이 접근했을 때
2. 토큰을 가지고 있다면 토큰이 유효한지 검증한다
3. 토큰으로부터 사용자 정보를 꺼내온다. 꺼내오는 함수의 내부는 다음과 같다
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("auth").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
Long userId = Long.parseLong(claims.get("userId").toString());
String email = claims.get("email").toString();
CustomUserDetails userDetails = (CustomUserDetails)new CustomUserDetails(userId, email);
return new UsernamePasswordAuthenticationToken(userDetails, token, authorities);
}
@AuthenticationPrincipal CustomUserDetails user처럼 컨트롤러에서 사용할 수 있도록 만들기 위함
UsernamePasswordAuthenticationToken = Username + Password + Authentication + Token
"사용자 인증 요청"과 "인증 완료 결과"를 모두 표현할 수 있는 Authentication 구현체로
SecurityContextHolder에 들어갈 수 있는 공식 타입
Token = 인증 정보를 담아 운반하는 객체
(X) 암호화 토큰
(X) 서명 토큰
=> 그냥 ‘인증 데이터 묶음’
4. 꺼내온 정보가 담긴 authentication을 SecurityContextHolder에 저장한다
SecurityContext
정의
현재 요청의 인증 결과를 담아두는 보관함
활용처
인증 후 최종 인증 결과를 담아 SecurityContext에 보관되고 필요할 때 전역적으로 참조
예시 )
@PreAuthorize("hasRole('SELLER')")
@AuthenticationPrincipal CustomUserDetails user
구성 요소

동작 원리
각 스레드마다 할당되는 고유 공간인 ThreadLocal에 저장되기 때문에 동일한 스레드인 경우 필요한 아무 곳에서나 참조가 가능
이해가 안되는 부분
jwt 인증으로 사용자 정보를 UsernamePasswordAuthenticationToken를 생성하여 반환할때 다음과 같은 형태인데
@AuthenticationPrincipal CustomUserDetails user가 가능한 이유가
return new UsernamePasswordAuthenticationToken(claims.getSubject(), token, authorities);
CustomUserDetails는
"인증이 끝난 사용자 정보를
Spring Security 전반에서 쓰기 위해" 사용
CustomUserDetails에는 무엇을 저장해야하는가?
userId만 저장해도 되는가?
권한을 저장해야하는가? -> no
customeUserDetails의 일부인데
@Override
public String getUsername() {
return "";
}
2025-12-17T17:45:14.124+09:00 WARN 11503 --- [yongeun-market] [nio-8080-exec-3] o.s.s.c.bcrypt.BCryptPasswordEncoder : Empty encoded password