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

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);
}
- 사용자 정보를 UsernamePasswordAuthenticationToken​를 생성하여 반환하는데 이름때문에 많이 헷갈렸는데 이는 토큰이 아니라 Authentication 구현체다
- jwt에서 userId와 email을 꺼내 CustomUserDetails를 만들어 전달한다 
=> 의도 securityContext에 CustomUserDetails를 담아서 

@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

+ Recent posts