Spring Security

2025. 6. 13. 10:50spring

반응형

Spring Security는 Spring 기반 애플리케이션에서 인증(Authentication)과 권한 부여(Authorization)를 처리해주는 보안 프레임워크입니다. 보안은 모든 웹 애플리케이션의 필수 요소이며, Spring Security는 이 과정을 강력하고 유연하게 구현할 수 있도록 도와줍니다.

 

🛡️ 1. Spring Security의 핵심 개념

1-1. 인증 (Authentication)

  • 사용자가 누구인지 확인하는 절차입니다.
  • 주로 ID/PW 입력 → 검증 → 로그인 성공 or 실패
  • 결과로는 Authentication 객체가 생성됩니다.

1-2. 권한 부여 (Authorization)

  • 인증된 사용자가 어떤 리소스에 접근할 수 있는지 결정하는 절차입니다.
  • 예: 관리자만 접근 가능한 페이지

🔄 2. Spring Security의 기본 동작 흐름

[1] 사용자가 로그인 요청 (/login)
  ↓
[2] UsernamePasswordAuthenticationFilter가 요청을 가로챔
  ↓
[3] AuthenticationManager가 사용자 인증 처리 (UserDetailsService → DB 조회)
  ↓
[4] 인증 성공 시 Authentication 객체 생성 및 SecurityContext에 저장
  ↓
[5] 인증된 사용자만 접근 가능한 리소스에 접근
  ↓
[6] 권한 확인 후 접근 허용 or 거부

 

🧱 3. 주요 구성 요소 설명

✅ 3-1. SecurityContextHolder

  • 인증 정보를 담는 객체입니다.
  • Authentication 객체를 담고 있고, 현재 사용자의 보안 정보를 어디서든 조회할 수 있게 합니다.

✅ 3-2. Authentication

  • 인증에 대한 정보를 담고 있는 인터페이스입니다.
  • 주요 속성:
    • principal: 사용자 정보 (User 객체 등)
    • credentials: 비밀번호 (보통 인증 후에는 삭제됨)
    • authorities: 권한 리스트 (ROLE_USER, ROLE_ADMIN 등)

✅ 3-3. UserDetails / UserDetailsService

  • UserDetails: 사용자 정보를 담는 인터페이스
  • UserDetailsService: DB에서 사용자를 조회하는 서비스
public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

✅ 3-4. AuthenticationManager

  • 인증 처리를 담당하는 핵심 인터페이스
  • authenticate() 메서드로 인증을 시도

✅ 3-5. FilterChainProxy

  • 다양한 필터들을 순서대로 적용합니다.
  • 예: UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter, ExceptionTranslationFilter 등

 

🔍 Spring Security 필터 체인 개념

Spring Security는 FilterChainProxy라는 필터를 통해 수많은 보안 필터들을 체인으로 연결해서 동작합니다.

DispatcherServlet
   ↓
FilterChainProxy (SpringSecurityFilterChain)
   ↓
  ├── SecurityContextPersistenceFilter
  ├── UsernamePasswordAuthenticationFilter
  ├── ExceptionTranslationFilter
  ├── FilterSecurityInterceptor
  └── ...

 

  • 요청이 들어오면 FilterChainProxy가 실행되고,
  • 등록된 여러 보안 필터들이 정해진 순서대로 실행됩니다.

📌 주요 필터들 순서와 역할

여기서는 Spring Security의 주요 필터들을 실제 동작 순서 기준으로 설명합니다:

1). SecurityContextPersistenceFilter

  • 요청 시작 시 SecurityContext를 읽어와서 저장하고,
  • 요청 완료 후 SecurityContext를 저장소(Session)에 저장합니다.
  • SecurityContextPersistenceFilter는 Spring Security 필터 체인에서 가장 먼저 실행되는 핵심 필터 중 하나로,
    사용자의 인증 정보를 담고 있는 SecurityContext를 관리하는 역할을 합니다.
  • 인증된 사용자 정보는 SecurityContextHolder.getContext().getAuthentication()을 통해 얻습니다.
  • 이 정보가 매 요청마다 유지되려면 세션 등에서 저장/복원하는 처리가 반드시 필요합니다.
  • 이 역할을 자동으로 해주는 게 바로 SecurityContextPersistenceFilter입니다.

📦 예: 이전 로그인 정보를 세션에서 꺼내와서 현재 요청에 연결함


2). LogoutFilter

  • 요청 URL이 로그아웃 경로(/logout)이면 처리
  • 로그아웃 후 세션 무효화, 쿠키 제거, SecurityContext 비우기

3). UsernamePasswordAuthenticationFilter

  • 로그인 처리 담당 필터
  • 기본적으로 /login에 POST로 요청 시,
    • username, password를 추출하고
    • AuthenticationManager로 인증 시도

🛠 커스텀 로그인 로직 만들고 싶을 때 이 필터를 확장하면 됨


4). BasicAuthenticationFilter

  • HTTP 기본 인증(Authorization: Basic ...)을 처리
  • API 호출 시 유용 (Postman 등에서)

5). BearerTokenAuthenticationFilter (JWT 사용 시)

  • 요청 헤더에 담긴 JWT 토큰을 파싱하고 인증 객체 생성
  • Spring Security 5.7+ 이후 사용 가능

❗ JWT 기반 인증할 때 사용하는 필터입니다


6). SecurityContextHolderFilter (Spring Security 6+)

  • SecurityContext를 쓰레드에 보관하는 필터 (기존엔 SecurityContextPersistenceFilter가 하던 일 분리됨)

7). RequestCacheAwareFilter

  • 인증 후 이전 요청 URL로 리다이렉트할 수 있게 요청 캐싱 처리

8). AnonymousAuthenticationFilter

  • 인증되지 않은 사용자인 경우, **익명 사용자(AnonymousUser)**로 인증 객체 생성
  • 권한 체크 시 "ROLE_ANONYMOUS"로 분류됨

9). ExceptionTranslationFilter

  • 필터 중간에서 예외(Exception) 발생 시
    • 인증 예외: 로그인 페이지로 리다이렉트
    • 인가 예외: 접근 거부 페이지로 이동

❗ 이 필터가 예외 처리의 핵심입니다


10). FilterSecurityInterceptor

  • 실제로 **권한 체크(인가)**가 이뤄지는 핵심 필터
  • 사용자의 권한(ROLE)이 요청 URL에 접근 가능한지 검사

 

 

 

 

로그인 예시

🔧 4. Spring Security 설정 (SecurityConfig)

@Configuration
@EnableWebSecurity
//@EnableWebSecurity는 Spring Security의 필수 설정 클래스인 WebSecurityConfigurerAdapter를 
//활성화하거나, 직접 설정한 SecurityFilterChain을 Spring Security에 연결해주는 역할을 합니다.
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable() // 필요에 따라
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/register", "/login", "/css/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login") // 커스텀 로그인 페이지
                .loginProcessingUrl("/loginProc") // 실제 로그인 처리 요청 경로
                .usernameParameter("username") // 폼 input name
                .passwordParameter("password") // 폼 input name
                .successHandler(loginSuccessHandler) // 로그인 성공 핸들러
                .failureHandler(loginFailureHandler) // 로그인 실패 핸들러
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            );

        return http.build();
    }

    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder())
                .and()
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

로그인 성공 처리 핸들러

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication)
            throws IOException, ServletException {

        // 사용자 정보
        User user = (User) authentication.getPrincipal();
        System.out.println("로그인 성공: " + user.getUsername());

        // 리다이렉트
        response.sendRedirect("/home");
    }
}

 

로그인 실패 처리 핸들러

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception)
            throws IOException, ServletException {

        // 실패 이유 로그
        System.out.println("로그인 실패: " + exception.getMessage());

        // 로그인 페이지로 이동 (실패 메시지 전달 가능)
        response.sendRedirect("/login?error=true");
    }
}

 

    •  

 

코드 설명

@Configuration
@EnableWebSecurity
public class SecurityConfig {
  • @Configuration: 이 클래스가 Spring 설정 클래스임을 명시.
  • @EnableWebSecurity: Spring Security를 활성화시키는 어노테이션입니다.
    → Spring Boot 사용 시 기본 보안 설정을 무시하고, 커스텀 설정을 적용하겠다는 의미입니다.

DI (의존성 주입)

@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private LoginFailureHandler loginFailureHandler;
  • 우리가 직접 만든 클래스를 Spring이 자동으로 주입합니다.
    • CustomUserDetailsService: DB에서 사용자 정보를 가져옴
    • LoginSuccessHandler: 로그인 성공 시 동작 정의
    • LoginFailureHandler: 로그인 실패 시 동작 정의

SecurityFilterChain - Spring Security 설정의 핵심

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

 

Spring Security 5.7 이후부터는 WebSecurityConfigurerAdapter 대신 SecurityFilterChain Bean 등록 방식을 사용합니다.

 

    http.csrf().disable()

 

  • CSRF(Cross Site Request Forgery)는 웹 공격 방지용 보안 기능입니다.
  • REST API나 단순한 로그인 테스트에는 주로 비활성화합니다.
    → 단, 운영 환경에서는 가능하면 켜두는 걸 권장합니다.
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/register", "/login", "/css/**").permitAll()
        .anyRequest().authenticated()
    )

 

  • requestMatchers("/register", "/login", "/css/**").permitAll()
    → 회원가입, 로그인, CSS 파일은 누구나 접근 가능
  • anyRequest().authenticated()
    → 나머지 모든 요청은 로그인한 사용자만 접근 가능
    .formLogin(form -> form
        .loginPage("/login") // 커스텀 로그인 페이지
        .loginProcessingUrl("/loginProc") // 로그인 submit 경로
        .usernameParameter("username") // 폼 input name
        .passwordParameter("password") // 폼 input name
        .successHandler(loginSuccessHandler) // 로그인 성공시 실행될 핸들러
        .failureHandler(loginFailureHandler) // 로그인 실패시 실행될 핸들러
        .permitAll()
    )

 

  • loginPage("/login"): 커스텀 로그인 HTML 페이지
  • loginProcessingUrl("/loginProc"):
    폼에서 POST로 요청 시, Spring Security가 자동으로 로그인 처리
  • usernameParameter, passwordParameter:
    폼에서 사용하는 input 태그의 name 속성과 매칭됨
  • successHandler, failureHandler:
    로그인 성공/실패 시, 우리가 만든 클래스가 실행됨
    .logout(logout -> logout
        .logoutSuccessUrl("/login?logout")
        .permitAll()
    );

 

  • 로그아웃 처리 후 이동할 URL 설정
    (기본 로그아웃 요청 경로는 /logout)
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
    return http.getSharedObject(AuthenticationManagerBuilder.class)
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder())
            .and()
            .build();
}
  • Spring Security 내부에서 사용하는 AuthenticationManager를 수동 설정합니다.
  • userDetailsService(userDetailsService)
    → 로그인 시 username을 기반으로 사용자를 조회
  • passwordEncoder(passwordEncoder())
    → 비밀번호 비교 시 사용될 암호화 방식 (BCrypt 사용)
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

 

  • 비밀번호 암호화에 사용할 PasswordEncoder Bean 등록
  • BCryptPasswordEncoder는 가장 널리 쓰이는 보안 해시 함수 중 하나입니다.
    회원가입 시 반드시 비밀번호를 이 인코더로 암호화 후 저장해야 합니다.

 

 

🔁 전체 동작 흐름

  1. 사용자가 /login에 접속 → 로그인 폼 표시
  2. 로그인 폼에서 /loginProc로 POST 요청
  3. Spring Security가 로그인 처리
    • CustomUserDetailsService.loadUserByUsername() 호출
    • userDetailsService에서 DB 사용자 정보 조회
    • BCryptPasswordEncoder로 비밀번호 비교
  4. 성공 → LoginSuccessHandler 호출 후 리다이렉트
    실패 → LoginFailureHandler 호출 후 로그인 페이지로 리턴
  5. /logout 호출 시 로그아웃 수행, /login?logout으로 이동

 

 

 

반응형

'spring' 카테고리의 다른 글

CSRF (Cross-Site Request Forgery)  (1) 2025.06.13
스프링 JWT(JSON Web Token) 인증  (1) 2025.06.13
Spring JDBC  (1) 2025.06.13
스프링 MVC  (1) 2025.06.13
스프링 컨테이너  (0) 2025.06.13