Skip to content

Spring Security 동작 원리

Yeongseo Na edited this page Jan 14, 2025 · 2 revisions

개요

우리 프로젝트에서 사용하는 Spring Security의 동작 원리에 대해 설명합니다.

image

이미지 출처

동작 원리


1. 요청 수신

  • 사용자의 요청이 서버에 도착한다.

2. 필터에서 요청 가로채기

  • 요청이 서블릿에 도달하기 전에, AuthoticationFilter가 요청을 가로채서 인증을 시작한다.
  • 코드 상으로, 스프링 시큐리티에 대한 @Configuration에서 설정한 SecurityFilterChain이 이에 해당한다.
  • 필터에서 JWT 토큰 검증, 로그아웃 사용자 처리 등의 일을 수행할 수 있다.
  • 앞으로의 인증 과정에서, 인증 정보를 담을 객체 Authentication를 생성하고 manager에 전달한다.

3. 인증 위임

  • AuthenticationManager는 등록된 AuthenticationProvider 중 적절한 것을 찾아 인증한다.

4. 사용자 정보 조회

  • AuthenticationProvider는 UserDetailsService 를 통해 UserDetails를 조회, 반환한다.
  • UserDetails 객체에는 사용자명, 비밀번호, 권한 목록 등 인증 및 인가에 필요한 정보가 담겨있다.

5. 인증 검증

  • AuthenticationProvider는 제공된 자격 증명과 UserDetails에 담긴 정보를 비교하여 사용자를 인증한다.
  • 그리고 그 결과를 Authentication 객체에 담아 반환한다.
  • Authentication 객체에는 인증 성공 여부, 권한 목록, 인증 대상(principal), 추가 메타데이터가 담긴다.
  • 이때, Authentication의 principal 필드에는 UserDetails가 들어간다.

6. Authentication 저장

  • Authentication 객체를 SecurityContextHolder에 저장한다.
  • SecurityContextHolder는 ThreadLocal 기반으로 동작하여, 각 요청별로 독립된 보안 컨텍스트를 유지하게 한다.

7. 인증 정보 활용

  • 컨트롤러나 서비스 계층에서, SecurityContextHolder에 담긴 인증 정보를 활용할 수 있다.
    • Principal 객체를 인자로 선언하면, 내부적으로 Authentication.getPrincipal()에 접근해서 Principal 를 인자로 주입해준다.
    • @AuthenticationPrincipal 애너테이션을 사용하면, UserDetails를 직접 주입받을 수 있다.

cf. Authentication vs UserDetails 차이
UserDetails는 인증과 인가 자체에 대한 정보만 담지만,
Authentication 는 그 과정에 대한 정보, 추가로 필요한 메타 데이터 등까지 포함한다.


cf. 우리 프로젝트에서는 JWT 토큰을 사용하므로 복잡한 인증이 필요하지 않다.
따라서 토큰과 관련된 과정은 3-5단계를 2단계인 필터에서 진행하도록 단순화했다.


JWT with Spring Security

  • JWT 는 Header, Payload/Claim, Signature 세개의 구성으로 이루어져있다.
  • 각 부분은 Base64로 인코딩되어 . 으로 구분된다.
  • Claim 중에서도 사용자를 식별하기 위한 정보를 Subject 라고 한다. (주로 이메일이나 사용자 id)

👇 claim 예시

{
	"sub": "john@example.com",  // Subject 
	"iat": 1516239022,  // Issued At 
	"exp": 1516242622,  // Expiration Time 
	"name": "John Doe",  // Custom Claim 
	"role": "ADMIN"  // Custom Claim 
}

스프링에서 JWT를 만들 때는 Jwt builder를 사용해서 만든다.

public String generateToken(String email, TokenType tokenType) {  
    Claims claims = Jwts.claims().setSubject(email);  // subject 설정
    Date now = new Date();  
    Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime());  
    return Jwts.builder()  
            .setClaims(claims)  
            .setIssuedAt(now)  
            .setExpiration(expiredDate)  
            .signWith(SignatureAlgorithm.HS512, this.secretKey)  
            .compact();  
}

Clone this wiki locally