Spring Security

 

Spring Security의 모든 것을 차근차근 정리한 글입니다. 복잡해 보이지만 하나씩 차근차근 정리하였습니다.

 

1. Spring Security란 무엇인가?

Spring Security의 정체

Spring Security는 Spring과는 별개의 독립적인 프로젝트다. 즉, Spring Framework가 제공하는 보안 전용 프레임워크라고 볼 수 있다.

주요 특징들

장점:

  • 인증, 권한 부여, 일반적인 공격(CSRF)에 대한 보호 기능을 제공한다
  • 더 이상 세션에만 의존하지 않고 자체적인 보안 방식을 제공한다
  • Filter 기반으로 동작하므로 모든 Servlet Application에서 동작 가능하다 → 컨테이너에 무관하게 적용 가능하다
  • @Secured 애노테이션으로 관심사 분리가 가능하다
  • DB 저장 방식과 인증 방법이 다양하다 (단순 ID/PW 외에도 다양한 방식 지원)

단점:

  • 구조가 복잡하다 ㅜㅜ

하지만 복잡한 만큼 강력한 기능을 제공한다는 점에서 학습할 가치가 충분하다.


2. 핵심 개념 이해하기

기본 용어 정리

용어 설명

Principal 사용자(계정)를 의미한다
Secured Resource 보안이 적용되는 리소스 (URL, API 엔드포인트, 파일 등)
Authentication 인증 - 로그인 과정 (아이디/패스워드 체크)
Authorization 인가 - 권한 체크 (role 기반으로 접근 권한 확인)

Authentication과 Authorization의 관계

이 둘의 관계가 Spring Security 이해의 핵심이다:

Authentication (1차 관문) → Authorization (2차 관문)
  • Authentication이 1차 문이다
  • Authentication을 통과해도 Authorization에서 실패할 수 있다 (예: admin이 아닌 일반 member인 경우)

핵심 정리: Authentication에 성공한 principal들이 secured resource들을 사용하기 위해서 authorization이 있는지 확인하는 과정이 Spring Security의 전체 흐름이다.


3. Filter 기반 동작의 비밀

왜 Filter를 사용할까?

많은 사람들이 궁금해하는 부분: "Interceptor에서도 보호하면 되는 거 아닌가?"

답: Spring Security는 Spring Application뿐만 아니라 모든 Web Application에서도 보호할 수 있게끔 설계되었기 때문이다.

Filter vs Spring의 근본적 차이

Filter의 특성

  • Filter는 순수한 Web 기술이다 (Spring 기반 동작이 아니다)
  • Servlet 앞단에 위치한다
  • Spring의 Bean을 직접 알 수 없다

Spring의 특성

  • Spring Bean이 동작하는 시점은 DispatcherServlet이 초기화한 이후다
  • Spring Security는 Spring 기반이므로 Bean 기반이다

문제 상황과 해결책

문제: Filter는 Spring을 모르는데 어떻게 Spring Bean을 사용할 수 있을까?

해결책: DelegatingFilterProxy를 통한 다리 역할

DelegatingFilterProxy의 역할

바로 이 경계를 연결하는 것이 DelegatingFilterProxy다:

Servlet Filter (Servlet Container 영역)
         ↓
DelegatingFilterProxy (다리 역할)
         ↓
Spring Security Filter (Spring 영역)

Servlet Container는 Spring을 모르기 때문에 Spring Security Filter를 직접 사용할 수 없다. 그래서 중간에 다리 역할을 하는 DelegatingFilterProxy가 필요하다.

동작 원리: 메모리의 ApplicationScope에 저장된 ApplicationContext 레퍼런스들을 가져와서 Bean들을 사용할 수 있게 만들어준다.

Servlet Container와 Spring의 관계

이 부분이 많은 개발자들이 헷갈리는 지점이다:

Servlet Container (예: Tomcat, Jetty)

  • Oracle에서 정의한 Servlet API 구현체 (자바 표준 기술)
  • Spring과는 별개의 독립적인 소프트웨어다
  • HTTP 요청을 받아서 Servlet으로 전달하는 역할을 담당한다
  • Spring 없이도 독립적으로 동작 가능하다

Spring WebApplicationContext

  • Spring Framework에서 제공하는 컨테이너다
  • Spring Bean들을 관리하는 공간이다
  • Servlet Container 위에서 동작한다

실제 구조

┌─────────────────────────────────┐
│ Servlet Container (Tomcat)      │ ← 자바 표준
│ ┌───────────────────────────┐   │
│ │ Spring WebApplicationContext│ ← Spring 제품
│ │ - Security Filters        │   │
│ │ - Controllers             │   │
│ │ - Services                │   │
│ └───────────────────────────┘   │
└─────────────────────────────────┘

 


4. 인증(Authentication) 과정 완전 분석

전체 흐름 개요

사용자의 최종 목적은 Secured Resource (엔드포인트, URL 등)를 사용하는 것이다.

Secured Resource는 보통 Servlet으로 구현되어 있고, Servlet으로 가기 전에 Filter를 거쳐야 한다. 그 중에서도 DelegatingFilterProxy가 Spring과 연동되는 부분이며, 이때 Security Filter Chain을 구동시킨다.

Security Filter Chain의 핵심 구성요소

Security Filter Chain에는 중요한 2가지 Filter가 있다:

  • Authentication Filter: 인증 담당
  • Authorization Filter: 권한 담당

즉, Security는 Filter 기반으로 동작한다.

Authentication Filter 상세 동작

1단계: Authentication 객체 생성

  • Filter를 통해서 제일 처음으로 인증을 거친다
  • 인증과 관련된 Authentication 객체를 생성한다
  • 이때 principal(=username), credential(=password)를 포함한다

2단계: AuthenticationManager로 위임

  • Authentication 객체를 AuthenticationManager로 전달한다
  • 구현체는 ProviderManager다

3단계: AuthenticationProvider 선택

  • AuthenticationProvider는 인증하는 방법을 알고 있는 구성요소다
  • 다양한 방법 중에서 현재 상황에 맞는 Provider를 선택한다 (여기서는 ID/PW 방식)

4단계: UserDetailsService 호출

  • AuthenticationProvider가 UserDetailsService를 호출한다
  • UserDetailsService에서 사용자 정보를 조회하여 UserDetails 객체를 구성한다
  • 사용자 정보는 DB, properties, 메모리 등 다양한 위치에 있을 수 있다

5단계: 비밀번호 검증

  • username에 해당하는 principal을 이용하여 조회한다
  • 사용자가 입력한 credential(=password)와 UserDetails의 password를 비교한다

6단계: 결과 처리

실패하면:

  • AuthenticationException이 발생한다

성공하면:

  • Authentication 객체를 다시 수정한다
  • Principal을 UserDetails로 대체한다
  • Credential 부분은 보안상 제거한다 (더 이상 필요 없으므로)
  • Authorities(권한 정보)를 가져와 SecurityContext에 저장한다

여기까지가 인증 절차다.

음식점 비유로 이해하기

복잡한 과정을 음식점으로 비유하면 이해가 쉽다:

  1. 손님(사용자) → **웨이터(Filter)**에게 주문
    • "아이디: hong, 비밀번호: 1234로 로그인하고 싶어요"
  2. 웨이터(Filter) → **매니저(AuthenticationManager)**에게 전달
    • "이 손님 인증 처리해주세요"
  3. 매니저(AuthenticationManager) → **요리사(Provider)**에게 배정
    • "username/password 방식이니까 DaoAuthenticationProvider야, 너가 해!"
  4. 요리사(Provider) → **창고지기(UserDetailsService)**에게 재료 요청
    • "hong이라는 사용자 정보 가져다줘"
  5. 창고지기(UserDetailsService)DB에서 사용자 정보 조회
    • "hong 사용자 찾았어요! 비밀번호는 암호화된 값이고, 권한은 USER예요"
  6. 요리사(Provider) → 비밀번호 검증
    • "손님이 준 1234와 DB의 암호화된 비밀번호가 일치하나?"
  7. 검증 성공 → 역순으로 결과 전달
    • "인증 완료! 이제 Principal은 UserDetails, 권한은 USER입니다"
  8. 웨이터(Filter)SecurityContext에 저장
    • "인증된 사용자 정보를 저장해두었습니다"

5. 인가(Authorization) 과정 이해하기

Authorization Filter 동작

인증이 완료되면 이제 권한을 확인해야 한다. 이때 AuthorizationFilter가 동작한다.

1단계: SecurityContext에서 인증 정보 획득

  • Authorization Filter는 SecurityContext에 저장된 사용자 인증 정보를 확인한다
  • 사용자가 권한을 가지고 있는지 SecurityContext를 통해 확인한다

2단계: AuthorizationManager로 판단 위임

  • 획득한 인증 정보를 AuthorizationManager에게 넘겨준다
  • 사용자가 원하는 페이지를 볼 수 있는지 판단을 위임한다

3단계: 결과 처리

  • 볼 수 있으면: continue (계속 진행)
  • 볼 수 없으면: AccessDeniedException 발생

Authentication 객체의 GrantedAuthority 활용

Authorization 과정에서는 Authentication 객체에 포함된 GrantedAuthority 목록을 사용하여 인가를 진행한다.


6. SecurityContext의 이중 저장 전략

SecurityContextHolder를 통한 접근

SecurityContext는 SecurityContextHolder를 통해 접근할 수 있다.

1. 외부적 관점: 세션 저장

  • 사용자의 인증 정보가 HTTP 세션에 저장된다
  • 브라우저와 서버 간의 상태 유지를 위해 사용된다
  • 키 이름은 SPRING_SECURITY_CONTEXT다

2. 내부적 관점: ThreadLocal 저장

  • 동일한 요청 처리 스레드 내에서는 ThreadLocal에 저장된다
  • SecurityContextHolder를 통해 언제든지 접근 가능하다

ThreadLocal을 사용하는 이유

웹 요청 처리 방식 이해

클라이언트 요청 → WAS → 스레드 생성 → 서블릿 처리
  • 각 HTTP 요청마다 별도의 스레드가 생성된다
  • 해당 스레드가 서블릿을 실행하여 요청을 처리한다
  • ThreadLocal에 인증 정보를 저장하면, 같은 스레드 내 어디서든 현재 사용자 정보에 접근 가능하다

각 저장 방식의 한계와 해결책

ThreadLocal만 있다면?

1차 요청: 로그인 성공 → ThreadLocal에 저장
1차 요청 끝 → 스레드 종료 → ThreadLocal 정보 사라짐! 💥
2차 요청: 새 스레드 생성 → ThreadLocal 비어있음
서버: "누구세요?" 😕

세션만 있다면?

controller에서 session.getAttribute()
service에서 또 session.getAttribute()  
repository에서 또 session.getAttribute()

매번 세션에서 정보를 꺼내와야 하므로 번거롭다.

둘 다 있으면? (현재 방식)

요청 시작: 세션 → ThreadLocal 복사 (한 번만)
처리 중: ThreadLocal에서 빠르게 접근 (여러 번)
요청 끝: ThreadLocal → 세션 저장 (한 번만)

REST API에서의 필요성

REST API의 특성

  • REST는 무상태(Stateless) 원칙을 따른다
  • 세션을 사용하지 않는 경우가 많다
  • JWT 토큰 등을 통한 인증 방식을 사용한다

실용적 이유

  • 세션에서 매번 인증 정보를 꺼내오는 것보다 ThreadLocal에서 바로 접근하는 것이 더 효율적이다
  • 코드 어디서든 SecurityContextHolder.getContext()로 간단하게 접근 가능하다
  • 매개변수로 매번 전달할 필요가 없어 코드가 간결해진다

결론: HTTP가 무상태라서 요청 간 정보를 기억하려면 세션이 필요하고, 요청 처리 중 편리하게 사용하려면 ThreadLocal이 필요하다.

ThreadLocal의 특성

ThreadLocal은 각 스레드마다 독립적인 변수 공간을 제공하는 Java 클래스다. 일반 변수는 모든 스레드가 공유하지만, ThreadLocal 변수는 각 스레드마다 독립적이다.


7. Authentication Interface 설계 철학

왜 Interface를 사용했을까?

Authentication Interface 구조

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated);
}

다양한 구현체가 필요한 이유

1. 다양한 인증 방식 지원

// 구현체 1: 사용자명/비밀번호 방식
UsernamePasswordAuthenticationToken

// 구현체 2: JWT 토큰 방식  
JwtAuthenticationToken

// 구현체 3: OAuth 방식
OAuth2AuthenticationToken

// 구현체 4: 익명 사용자
AnonymousAuthenticationToken

2. 인증 방식에 관계없이 동일한 처리

// 어떤 Authentication 구현체든 상관없이 동일하게 처리
public void someMethod(Authentication auth) {
    String username = auth.getName();       // 공통 메소드
    Collection<GrantedAuthority> authorities = auth.getAuthorities(); // 공통 메소드
    // 구현체가 뭐든 상관없이 동일하게 사용 가능!
}

인증 전후의 변화

인증 전 (UsernamePasswordAuthenticationToken)

// 인증 전: Credentials에 비밀번호, Principal에 사용자명
Authentication authRequest = new UsernamePasswordAuthenticationToken(
    "hong",    // Principal (사용자명)
    "1234",    // Credentials (비밀번호)  
    null       // Authorities (아직 없음)
);

인증 후 (같은 클래스지만 내용이 바뀜)

// 인증 후: Principal이 UserDetails로, Credentials는 null로, Authorities 추가
Authentication authResult = new UsernamePasswordAuthenticationToken(
    userDetails,  // Principal (UserDetails 객체)
    null,         // Credentials (보안상 제거)
    authorities   // Authorities (권한 정보)
);

핵심 설계 철학

같은 Authentication 인터페이스를 사용하지만:

  • 인증 전: 사용자가 제공한 정보를 담는다
  • 인증 후: 검증된 사용자 정보를 담는다

이렇게 인터페이스로 설계해서 다양한 인증 방식을 통일된 방법으로 처리할 수 있게 만들었다.

Provider와 인증 실패 처리

  • 인증 방식에 따라 다양한 Provider들이 존재한다
  • ProviderManager가 순차적으로 인증을 요청한다
  • 마지막 Provider까지 인증 처리에 실패하면 ProviderNotFoundException이 발생한다

8. 실제 설정과 구현

AutoConfiguration의 동작

Spring Boot의 AutoConfiguration 덕분에 기본 설정이 자동으로 만들어진다.

@EnableWebSecurity 사용법

@EnableWebSecurity는 Configuration 클래스에 붙여야 한다.

권장하는 방식

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/home", "/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")  
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            )
            .logout(logout -> logout.permitAll())
            .build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

@EnableWebSecurity가 하는 일

자동으로 Import되는 구성들

@EnableWebSecurity
// 내부적으로 이런 일들이 일어난다:
// @Import({
//     WebSecurityConfiguration.class,
//     SpringWebMvcImportSelector.class, 
//     OAuth2ImportSelector.class,
//     HttpSecurityConfiguration.class
// })

활성화되는 기능들

  • ✅ SecurityFilterChain 자동 생성
  • ✅ AuthenticationManager 설정
  • ✅ CSRF 보호 활성화
  • ✅ 세션 관리 설정
  • ✅ 보안 헤더 추가
  • ✅ 기본 로그인/로그아웃 페이지 제공

기본 동작 확인

모든 리소스에 대한 인증 요청

  • index 요청 시 login 페이지로 redirect된다

기본 사용자 제공

  • username: user
  • password: 로그의 "Using generated security password" 값

로그아웃

  • localhost:8080/logout으로 접근

이는 "당장 사용자 설정 없이도 로그인 테스트를 할 수 있게" 해주는 Spring Boot의 배려다.

PasswordEncoder 필수 사항

credential 암호화는 필수!

동일한 password를 가지고 암호화를 여러번 실행하여 변수에 나눠 담으면, 서로 다른 암호화 결과가 나온다.

비교할 때 equals 대신 matches 사용!

BCrypt는 salt를 해시 결과에 포함시켜 저장한다. 저장된 해시에서 salt를 추출한 후, 입력된 비밀번호를 같은 salt로 암호화하기 때문에 passwordEncoder.matches()를 사용해야 한다.

DaoAuthenticationProvider 상세

username/password 기반 인증

2개의 has-a interface 관계를 가진다: UserDetailsService, PasswordEncoder

UserDetailsService: 메모리 vs DB

  • JDBC 방식은 table 구조와 Query를 고정해둔다
  • 우리가 원하는 방식과 안 맞을 수 있다
  • SpringSecurity가 기대하는 고정된 테이블 구조가 우리 서비스와 다를 수 있다

해결책: 커스텀 UserDetailsService 구현

@Bean
UserDetailsService userDetailsService(PasswordEncoder pe) {
    UserDetails admin = User.builder().username("admin@safy.com")
        .password(pe.encode("1234"))
        .roles("ADMIN").build();
        
    UserDetails staff = User.builder().username("user@safy.com")
        .password(pe.encode("1234"))
        .roles("USER").build();
        
    return new InMemoryUserDetailsManager(staff, admin);
}

주의: pw 인코딩해서 넣는 것을 잊지 말아야 한다.

FilterChainProxy와 SecurityFilterChain

FilterChainProxy의 역할

  • "하청의 하청" 느낌임
  • Security를 동작시키는 SecurityFilterChain을 목록으로 관리
  • SecurityFilterChain들을 0번부터 n번까지 가짐

구조 이해

헷갈리는 부분: Authentication filter, Authorization filter도 SecurityFilterChain 종류 중 하나인가?

답: 아님! 구조를 정확히 이해해야 함.

올바른 구조

SecurityFilterChain ≠ 개별 Filter들

FilterChainProxy
├── SecurityFilterChain #1 (예: /api/** 경로용)
│   ├── AuthenticationFilter
│   ├── AuthorizationFilter  
│   ├── CsrfFilter
│   └── ... (기타 Security Filter들)
│
├── SecurityFilterChain #2 (예: /admin/** 경로용)
│   ├── AuthenticationFilter
│   ├── AuthorizationFilter
│   ├── CustomAdminFilter
│   └── ... (다른 설정의 Filter들)
│
└── SecurityFilterChain #3 (예: /public/** 경로용)
    └── ... (최소한의 Filter들)

정확한 관계

  • SecurityFilterChain = Filter들의 묶음
  • 하나의 SecurityFilterChain은 여러 개의 Security Filter들을 포함
  • 각 SecurityFilterChain은 특정 URL 패턴에 적용
  • Authentication Filter, Authorization Filter는 SecurityFilterChain 안에 들어있는 구성요소

다중 SecurityFilterChain 관리

  • FilterChainProxy는 여러 개의(0~n) SecurityFilterChain을 가질 수 있음
  • 경로 기반으로 순서대로 filter를 확인
  • **@Order(x)**를 통해 FilterChain 순서를 부여함 → 최초로 해당하는 경로 기반 chain에서 처리됨

Authorization 설정 상세

HttpSecurity#authorizedHttpRequests 사용

MockMvc를 사용하여 실제 서버 없이 웹 요청을 테스트할 수 있음:

@Test
void 인증없는_상태에서_어떤_엔드포인트_접근_확인() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/"))
        .andExpected(MockMvcResultMatchers.status().isOk());
}

경로 지정 방법들

특정 경로 지정

authorize.requestMatchers("/secured").authenticated()  
// /secured, /secured/, /secured.html에 대응

패턴 활용 (Ant 표현식)

  • ? : 1개의 문자 매칭
  • * : 단일 경로 세그먼트 내의 0개 이상의 문자 매칭
  • ** : 0개 이상의 경로 세그먼트 매칭
  • {pattern1, pattern2} : 여러 패턴 중 하나
  • [abc] : a, b, c 중 하나의 문자
.requestMatchers("/secured/**").authenticated()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/v1/*/*.json").hasRole("API_USER") 
.requestMatchers("/public/??/*.html").permitAll()
.requestMatchers("/images/*.{jpg,png,gif}").permitAll()
.requestMatchers("/static/[abc]/*.js").permitAll()

권한 설정 메서드들

메서드 설명

hasRole(role) 역할이 부여된 권한과 일치하는지 확인함
hasAnyRole(role...) 부여된 역할 중 일치하는 항목이 있는지 확인함
permitAll() 모든 요청자의 접근을 허가함
denyAll() 모든 요청자의 접근을 금지함
authenticated() 인증이 된 경우 허용함

권한 설정 순서의 중요성

잘못된 순서 (포괄적인 것이 먼저):

http.authorizeHttpRequests(authorize -> authorize
    .requestMatchers("/secured/**").authenticated()     
    .requestMatchers("/secured/admin/**").hasRole("ADMIN")  // 이미 위에서 걸려서 도달 불가!
    .anyRequest().permitAll());

올바른 순서 (세부적인 것이 먼저):

http.authorizeHttpRequests(authorize -> authorize
    .requestMatchers("/secured/admin/**").hasRole("ADMIN")  // 세부적인 것 먼저
    .requestMatchers("/secured/**").authenticated()         // 포괄적인 것 나중에
    .anyRequest().permitAll());

중요: 맨 위에서부터 순서대로 확인하고 true가 반환되면 중지한다. 따라서 세부적인 규칙을 먼저 처리해야 한다.

Role 관리와 계층 구조

Role은 enum 말고 문자열로도 가능

Role은 비즈니스 로직 상에서 그냥 정의하면 된다. Spring에서 만든 것이 아니다.

계층적 Role 관리

Role을 계층적으로 관리할 수 있다:

@Bean
RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.withDefaultRolePrefix()
        .role("ADMIN").implies("USER")
        .role("USER").implies("GUEST")
        .build();
}

문자열(블록 텍스트 """ 기반)으로도 정의 가능하다:

@Bean  
RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.fromHierarchy("""
        ROLE_ADMIN > ROLE_STAFF
        ROLE_STAFF > ROLE_USER
        """);
}

.role(A).implies(B): A가 B도 포함한다는 의미다.


9. 핵심 구성요소들의 역할 분담

왜 이렇게 복잡하게 나눴을까?

각 구성요소가 전문화된 역할을 담당하기 때문이다:

구성요소 전문 역할

Filter HTTP 요청 처리 전문
Manager 다양한 인증 방식 관리 (OAuth, JWT, Form 등)
Provider 특정 인증 방식의 구체적 처리
UserDetailsService 사용자 정보 조회 전문

FilterChainProxy와 SecurityFilterChain 구조

FilterChainProxy의 역할

  • "하청의 하청" 느낌이다
  • Security를 동작시키는 SecurityFilterChain을 목록으로 관리한다
  • SecurityFilterChain들을 0번부터 n번까지 가진다

구조 이해

헷갈리는 부분: Authentication filter, Authorization filter도 SecurityFilterChain 종류 중 하나인가?

답: 아니다! 구조를 정확히 이해해야 한다.

올바른 구조

SecurityFilterChain ≠ 개별 Filter들

FilterChainProxy
├── SecurityFilterChain #1 (예: /api/** 경로용)
│   ├── AuthenticationFilter
│   ├── AuthorizationFilter  
│   ├── CsrfFilter
│   └── ... (기타 Security Filter들)
│
├── SecurityFilterChain #2 (예: /admin/** 경로용)
│   ├── AuthenticationFilter
│   ├── AuthorizationFilter
│   ├── CustomAdminFilter
│   └── ... (다른 설정의 Filter들)
│
└── SecurityFilterChain #3 (예: /public/** 경로용)
    └── ... (최소한의 Filter들)

정확한 관계

  • SecurityFilterChain = Filter들의 묶음
  • 하나의 SecurityFilterChain은 여러 개의 Security Filter들을 포함한다
  • 각 SecurityFilterChain은 특정 URL 패턴에 적용된다
  • Authentication Filter, Authorization Filter는 SecurityFilterChain 안에 들어있는 구성요소

다중 SecurityFilterChain 관리

  • FilterChainProxy는 여러 개의(0~n) SecurityFilterChain을 가질 수 있다
  • 경로 기반으로 순서대로 filter를 확인한다
  • **@Order(x)**를 통해 FilterChain 순서를 부여한다 → 최초로 해당하는 경로 기반 chain에서 처리된다

10. 마무리 및 핵심 정리

Spring Security의 핵심 흐름

  1. Filter 기반 동작: Servlet Container와 Spring의 경계를 DelegatingFilterProxy가 연결
  2. Authentication → Authorization: 2단계 보안 검증 과정
  3. SecurityContext 이중 저장: 세션과 ThreadLocal을 모두 활용
  4. Interface 기반 설계: 다양한 인증 방식을 통일된 방법으로 처리

실무에서 기억해야 할 핵심 사항

보안 관련

  • PasswordEncoder 필수 사용: BCrypt 등을 활용한 암호화
  • matches() 메소드 사용: equals() 대신 passwordEncoder.matches() 사용
  • 권한 설정 순서: 세부적인 규칙을 먼저, 포괄적인 규칙을 나중에

구조 이해

  • Filter vs Interceptor: Filter는 모든 Web Application에서 동작 가능
  • Provider의 역할: 각 인증 방식별로 전문화된 처리
  • SecurityFilterChain: URL 패턴별로 다른 보안 정책 적용 가능

다음 단계 학습 방향

  • OAuth2와 JWT: 현대적인 인증 방식

 

Spring Security는 복잡하지만 그만큼 강력하고 유연한 보안 프레임워크다. 이 글에서 다룬 기본 구조를 바탕으로 점진적으로 고급 기능들을 학습해 나가면 된다.

'Spring' 카테고리의 다른 글

JWT with Spring Security  (4) 2025.06.11