실습을 통해서 알아보는 Spring Security 기초

2025. 1. 25. 01:50·Spring/기초 개념

Spring Security란

Spring Security는 인증, 인가뿐만 아니라 일반적인 공격으로부터 서비스를 보호하기 위해 사용하는 프레임워크이다.

 

Spring Security는 자바 17버전 이상의 런타임 환경을 요구한다. 

 

Spring Security 동작 방식

 

Spring Security는 아래와 같은 방식으로 동작한다.

각 과정을 하나씩 살펴보로독 하자.

  1. HTTP 요청을 AuthenticationFilter가 가로챈다. (1)
  2. AuthenticationFilter는 인증을 하기 해서 사용자 인증 정보(Authentication 객체)을 생성한다. (2) 
    • AuthenticationFilter는 AuthenticationProvider에 인증 책임을 넘기고, 인증 결과에 따른 분기를 수행한다.
    • Authentication 객체는 인증 정보를 담는 객체이다.
      • 위 그림에서는 UsernamePasswordAuthenticationToken이 Authentication 객체이다.
    • 2번 시점에서는 아직 사용자 인증이 되지 않았기에 isAuthenticated()=false이고 principal와 credentials만 포함함
    • 인증 완료 시 isAuthenticated()=true이고 인증된 사용자 정보(principal)와 권한(authorities)를 포함함
  3. AuthenticationManager에게 Authentication 객체 전달한다. (3)
    • AuthenticationManager는 Authentication 객체의 정보를 인증하고 결과를 반환하는 역할을 한다.
  4. AuthenticationManager는 AuthenticationProvider에게 Authentication 객체를 전달한다. (4)
    • AuthenticationProvider는 구체적인 인증 로직을 구현한 곳이다.
    • DB에서 사용자 정보 조회, JWT 토큰 인증, OAuth2 인증 등이 이 계층에서 발생한다.
  5. AuthenticationProvider는 사용자 정보와 UserDetalService를 활용하여 UserDetails 객체를 받는다. (5, 6)
    • UserDetailService를 통해 UserDetails 객체를 생성한다.
    • UserDetails 객체는 인증 전 필요한 사용자 정보를 수집 및 캡슐화하기 위해 사용하는 객체
    • 필요하다면 PasswordEncoder로 password를 가공한다.
  6. UserDetails 객체로 사용자를 인증한 후, 인증 정보를 AuthenticationFilter에 반환한다. (7, 8, 9)
  7. AuthenticationFilter는 인증의 성공 여부에 따라 다르게 동작한다. (10)
    • SecurityContextHolder : SecurityContext를 감싸는 객체로 Authentication 객체가 저장되는 보관소 
    • 인증 성공시 : 
      • SecurityContextHolder에 Authentication 객체를 저장
      • 이 단계에서 Authentication에는 아래와 같은 정보들이 있다.
        • principal : 인증된 사용자 정보(UserDetails)
        • authorities : 사용자 권한
        • isAuthenticated() = true
      • 인증 성공 핸들러 실행 및 남아있는 Spring Security의 필터 실행
    • 인증 실패시 : 
      • SecurityContextHolder 초기화
      • 인증 실패 핸들러 실행

인증 방식마다 각 단계의 객체를 지칭하는 명칭에 차이가 있을 수는 있지만, 대부분 각 단계에서 파생된 객체이므로 원리는 비슷하다.

 

Spring Security 시작하기

 

스프링 부트 3.0부터는 Spring Security 버전 6 이상이 적용되어 있다.

기존의 and() 방식이 아닌 Lambda DSL 방식을 사용하는 것을 권장한다.

 

우선 초기에는 아래와 같이 spring-boot-starter-security 의존성을 가져온다

implementation 'org.springframework.boot:spring-boot-starter-security'

 

Spring Security 공식 문서에는 아래와 같은 코드를 제공한다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChan(HttpSecurity http) throws Exception{
        return http.build();
    }
}

코드를 간단하게 설명하겠다.

  • 어노테이션
    • @Configuration : @Bean들이 한 번씩만 생성되는 것을 보장함
    • @EnableWebSecurity : 스프링 시큐리티를 활성화함
  • SecurityFilterChain
    • Spring Security에서 제공하는 인증, 인가, 보호, 설정 관련 필터를 모아둔 객체
    • Spring Security는 해당 객체를 Bean으로 등록하여 관리함

정리하자면, Spring Security의 필터 목록들을 모아놓은 객체를 빈으로 등록한다고 볼 수 있다.

 

SecurityFilterChain에 새로운 필터 등록하기

 

이번에는 Spring Security의 SecurityFilterChain에 새로운 필터를 등록하는 것을 알아본다.

 

Spring Security 공식 문서에서는 아래와 같은 방식을 추천한다.

  • Exploit Protection Filter : SecurityContextHolderFilter 이후에 해당 필터가 위치
    • SecurityContextHolderFilter : SecurityContext를 SecurityContextHolder에 설정하는 필터
  • Authentication Filter : LogoutFilter 이후에 해당 필터가 위치
    • LogoutFilter : 세션 무효화, 인증 토큰 삭제 등 로그아웃에 대한 처리를 담당하는 필터
  • Authorization Filter : Authentication Filter 이후에 해당 필터가 위치

 

필터는 아래와 같은 메서드를 활용하여 설정할 수 있다.

  • addFilterBefore(대상필터, 기준필터) : 기준필터 이전에 대상필터 추가
  • addFilterAfter(대상필터, 기준필터) : 기준필터 이후에 대상필터 추가
  • addFilterAt(대상필터, 기준필터) : 기준필터 위치에 대상필터 추가

간단한 예시를 통해서 알아보자.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    static class HelloFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        FilterChain filterChain) throws ServletException, IOException {
            filterChain.doFilter(request, response);
        }
    }
    @Bean
    public SecurityFilterChain filterChan(HttpSecurity http) throws Exception{
        http
                .addFilterAfter(new HelloFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

이 코드는 HelloFilter라는 새로운 필터 클래스를 생성했고, UsernamePasswordAuthenticationFilter 이후에 배치한다.

 

여기서 아래와 같은 사실들을 알 수 있다.

  • 각 필터를 꼭 빈으로 등록할 필요는 없다.
  • HttpSecurity.addFilterAfter()와 같은 꼴로 필터를 등록할 수 있다

Spring Security Filter를 Bean으로 등록하기

필터도 당연히 빈으로 등록할 수 있다. 

하지만, Spring Boot는 @WebFilter 혹은 Filter 구현체를 servlet 컨텍스트에 자동 등록한다.

이는 @Bean와 @Component를 활용한 빈 등록과 중복될 수 있기에 문제가 발생할 수 있다.

 

FilterRegistrationBean<필터타입>을 사용하면 필터가 servlet 컨텍스트에 자동 등록되는 것을 막을 수 있다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    static class HelloFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(
                HttpServletRequest request,
                HttpServletResponse response,
                FilterChain filterChain) throws ServletException, IOException {
            filterChain.doFilter(request, response);
        }
    }
    @Bean
    public FilterRegistrationBean<HelloFilter> helloFilter(){
        FilterRegistrationBean<HelloFilter> bean = new FilterRegistrationBean<>();
        return bean;
    }
    @Bean
    public SecurityFilterChain filterChan(HttpSecurity http) throws Exception{
        http
                .addFilterAfter(new HelloFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

 httpBasic와 BasicAuthenticationFilter

아래와 같은 상황에서는 BasicAuthenticationFilter가 두번 등록되므로 문제가 발생할 수 있다.

httpBasic(Customizer.withDefaults)에서 BasicAuthenticationFilter를 자동으로 등록해주기 때문에 발생하는 문제이다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        BasicAuthenticationFilter basic = new BasicAuthenticationFilter(
                new AuthenticationManager() {
                    @Override
                    public Authentication authenticate(
                            Authentication authentication
                    ) throws AuthenticationException {
                        return null;
                    }
                }
        );
        http
                .httpBasic(Customizer.withDefaults())
                .addFilterAt(basic, BasicAuthenticationFilter.class);
        return http.build();
    }
}

 

이를 해결하기 위해서는 아래와 같이 httpBasic을 disable할 수 있다.

        http
                .httpBasic(b -> b.disable())

 

이처럼 필터를 추가하지 않았음에도 필터를 중복 추가했다는 문제가 발생한다면, httpBasic와 같은 기본 설정 메서드의 동작을 의심해보는 것이 좋다.

 

Spring Security에서의 Exception 핸들링과 요청 저장

ExceptionTranslationFilter

ExceptionTranslationFilter는 FilterChainProxy 내부에 포함된 필터 중 하나이다.

Security Exception을 HTTP 응답으로 반환해주는 역할을 한다.

ExceptionTranslationFilter 동작 과정

아래와 같은 방식으로 동작한다.

  1. FilterChain.doFilter 를 호출하여 필터 체인이 계속 실행되도록 한다.
  2. 예외가 발생했다면 예외의 종류에 따라서 처리 방식이 달라진다.
    • AuthenticationException 발생 시 처리
      • SecurityContextHolder를 초기화하여 잘못된 인증정보 제거
      • 추후 인증 성공 시 기존 요청을 재시도할 수 있도록 RequestCacahe에 요청 정보 저장
      • AuthenticationEntryPoint 호출하여 로그인 페이지로 리디렉션하거나 WWW-Authenticate 헤더가 포함된 응답을 받는다.
    • AccessDeniedExceptuon 발생 시 처리
      • 403 Forbidden 상태 코드를 반환하고 필요 시 커스텀 에러 메세지를 전달할 수 있다.

RequestCache로 요청 저장하기

RequestCache에 요청을 저장하여 추후 인증되었을 때 해당 요청을 다시 처리할 수 있다.

 

주요 구현체는 아래와 같다.

  • HttpSessionRequestCache (기본값)
    • HttpSession을 사용하여 요청을 저장한다.
    • 요청 매칭 조건을 설정하여, 해당 조건에 부합하는 경우에만 요청을 저장하도록 할 수 있다.
  • NullRequestCache
    • 요청 저장을 비활성화하기 위해서 사용한다.

종합하자면, 아래와 같은 방식으로 requestCache를 설정할 수 있다

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final String[] WHITE_LIST = {"/auth/login"};
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
//      캐시 객체 생성 및 매칭 조건 지정
        HttpSessionRequestCache cache = new HttpSessionRequestCache();
        cache.setMatchingRequestParameterName("continue");
        http
//              requestCache 설정
                .requestCache(rc -> rc.requestCache(cache));
        return http.build();
    }
}

 

마무리

 

이번에는 Spring Security에 대한 기초 개념을 간단한 예제를 통해서 다루었다.

 

다음에는 Spring Security를 활용하여 JWT 로그인과 필터를 생성해볼 예정이다.

'Spring > 기초 개념' 카테고리의 다른 글

Access Token와 Refresh Token을 활용한 인증과 인가  (0) 2025.02.07
Spring Boot에서 테스트코드 작성하기  (0) 2025.02.01
Spring Security로 JWT 토큰 인증 방식 구축하기  (1) 2025.01.27
예시를 통해 알아보는 Spring Boot의 의존성 주입(DI)  (1) 2025.01.19
Spring의 제어역전(IoC)  (0) 2024.12.26
'Spring/기초 개념' 카테고리의 다른 글
  • Spring Boot에서 테스트코드 작성하기
  • Spring Security로 JWT 토큰 인증 방식 구축하기
  • 예시를 통해 알아보는 Spring Boot의 의존성 주입(DI)
  • Spring의 제어역전(IoC)
코드래곤
코드래곤
코드래곤 님의 블로그 입니다.
  • 코드래곤
    코드래곤 님의 블로그
    코드래곤
  • 전체
    오늘
    어제
    • 분류 전체보기 (61)
      • 알고리즘 (3)
        • 그리디 (1)
        • 그래프 (2)
      • 시스템 설계 (6)
      • CS 및 기본 개념 (17)
      • Docker (5)
      • Spring (23)
        • 백준 서비스 구현하기 (1)
        • 기초 개념 (14)
        • MSA (2)
        • JPA (1)
      • Dart (3)
      • Flutter (1)
      • Kubernetes (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
코드래곤
실습을 통해서 알아보는 Spring Security 기초
상단으로

티스토리툴바