Spring Security

Spring Security는 인증, 인가 및 일반적인 공격에 대한 보호를 제공하는 프레임워크 입니다. 서비스를 개발할때 필수적으로 구현해야하는것이 로그인, 회원가입과 권한인데요. 이를 Spring Security를 통해 쉽게 해결할 수 있습니다. 다만 이 프래임워크가 워낙 방대하고 구조를 이해하고 있지않으면 활용하기 쉽지 않습니다. 하나의 게시물로 모든 Security 기능에 대해서 소개하지 못하므로, 이 글에서는 프레임워크에 대해 기본개념과 주요기능에 대해서 소개하고 각 기능에 대한 상세한 설명은 다음 글에서 이어가도록 하겠습니다.

 

이 글은 Spring Boot 3.4.0, Spring Security 6.4.1 환경에서 제작되었습니다.

 

 

 

들어가기전에

Security를 공부하기전에 짧게 기본개념을 잡고 가겠습니다. 이해하는데 도움이 될겁니다.

 

 

1. Filter

Spring Security는 Servlet Filter를 기반으로 동작합니다.

Client - Filter - DispatcherServlet - Interceptor - Controller

 

 

2. 용어

용어 설명
Authentication 인증
  • 사용자가 자신이 주장하는 사람이 맞는지 확인하는 과정
  • '신원 확인'의 과정
    예) 공항에서 여권으로 신원을 확인하는 것
Authorization 인가
  • 인증된 사용자가 특정 리소스에 접근할 수 있는 권한이 있는지 확인하는 과정
  • '권한 확인'의 과정
    예) 회사에서 직급에 따라 접근할 수 있는 시스템이 다른 것
Principal 접근주체
  • 시스템에 접근하는 사용자, 디바이스 또는 다른 시스템 등의 주체
  • 인증 이후 SecurityContext에 저장되는 인증된 사용자의 정보
    예) 사원증을 소지한 직원
Role 권한
  • 특정 리소스에 대한 접근 권한을 그룹화한 것
  • 사용자에게 부여될 수 있는 권한의 집합
    예) 회사에서의 직급 (사원, 대리, 과장 등)

 

용어 간의 관계

1. Authentication -> Principal 생성
   (인증 성공 시 Principal 정보가 생성됨)

2. Principal -> Role 부여
   (인증된 사용자에게 역할이 부여됨)

3. Role -> Authorization 결정
   (부여된 역할에 따라 권한이 결정됨)

 

 

3. SecurityContextHolder

SecurityContextHolder는 인증된 사용자의 정보를 저장하는 곳입니다.

 

 

4. Filter, Authentication, Authorization

Security에서 크게 Filter, Authentication, Authorization 으로 나눌 수 있습니다. 아키텍처를 볼 때, 이렇게 3개로 구분해서 보면 이해가 조금 더 쉬울 수 있습니다.

 

 

 

 

 

 

1. 아키텍처

 

출처 : https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html

 

시큐리티 공식문서에서 가져온 아키텍처입니다. 이 사진을 보고 이해해야할것은 아래와 같습니다.

 

  1. SecurityFilterChain 은 Filter에서 동작한다.
  2. Authentication을 통해 인증과정을 수행한다.
    인증에 성공하면 SecurityContextHolder에 인증된 사용자의 정보를 담는다.
    인증에 실패하면 SecurityContextHolder가 비워집니다.(cleared out)

 

 

2. Filter

 

2-1.  DelegatingFilterProxy

 

서블릿 컨테이너와 Spring의 ApplicationContext 사이의 연결점입니다. 서블릿 컨테이너는 자체 표준을 사용하여  필터 인스턴스를 등록할 수 있지만 Bean에 대해서는 알지 못합니다. 그래서 DelegatingFilterProxy 에 Bean 등록을 위임하도록 합니다.

 

한줄정리 : DelegatingFilterProxy는 Servlet Container - Spring ApplicationContext를 연결하는 역할을 한다!

 

 

2-2. FilterChainProxy과  SecurityFilterChain

 

  1. FilterChainProxy는 보안필터의 시작점입니다.
  2. 요청 URL에 따라(RequestMatcher) 적절한 SecurityFilterChain을 선택할 수 있도록 합니다.
  3. Spring Security의 HttpFirewall을 적용하여 특정 유형의 공격으로부터 애플리케이션을 보호합니다.
  4. FilterChianProxy는 bean 이기때문에 DelegatingFilterProxy로 감싸져있습니다.

 

 

요청 URL에 따라 SecurityFilterChain에서 Filter를 선택

 

 

 

2-3. SecurityFilterChain 내부 Filter

 

spring-security/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java at

Spring Security. Contribute to spring-projects/spring-security development by creating an account on GitHub.

github.com

SecurityFilterChain에는 여러 Filter가 존재하는데 그 Filter의 순서는  FilterOrderRegistration.class 를 통해 확인할 수 있습니다. Filter의 순서를 외울필요는 없지만 어느 정도 흐름은 알고있는게 좋습니다. 

각 Filter의 용도와 사용법은 다음 글에서 알아보도록하겠습니다.

 

 

 

 

3.  Authentication (인증)

사용자 정보를 가지고 인증을 하는 과정입니다.

 

3-1. SecurityContextHolder

 

 

  • SecurityContextHolder는 인증된 사용자의 정보를 담고있는 Container입니다. Spring Security는 SecurityContextHolder가 어떻게 채워지는지 묻지않습니다. 그저 값이 포함되어있으면 인증된 사용자로 인식합니다. 즉, SecurityContextHolder가 비어있으면 인증되지않은 사용자, 비어있지않으면 인증된 사용자로 인식됩니다.
  • ThreadLocal을 사용하여 보안정보를 저장합니다. 즉, SecurityContext는 동일한 스레드내에서는 어디서든 보안 정보를 쉽게 접근 가능합니다. 요청 처리가 끝나면 반드시 쓰레드의 정보를 지워야 하지만 FilterChainProxy가 이 청소를 자동으로 해주기때문에 매우 안전하게 사용할 수 있습니다.
  • ThreadLocal에 대해 안전하지않은 어플리케이션에 대해서는 ThreadLocal 설정을 변경할 수 있습니다.
 

[JAVA] ThreadLocal에 대해서 알아보자

개요Spring Security 에서 ThreadLocal에대해서 언급한적이 있어 글로 남겨봅니다. ThreadLocal이란?ThreadLocal은 Java에서 제공하는 클래스로, java.lang 패키지에 존재합니다.각 스레드마다 독립적인 변수를

tmd8633.tistory.com

 

3-2. SecurityContext

인증된 객체(Authentication)를 보관하는 역할을 합니다. SecurityContextHolder로 부터 가져올 수 있습니다.

 

 

3-3. Authentication

현재 접근하는 사용자의 정보와 권한을 담은 인터페이스 입니다. isAuthenticated() 메소드로부터 인증되었는지 확인할 수 있습니다.

 

용어 설명
principal 아이디/비밀번호로 인증할때 사용자를 식별하는 역할을 합니다. UserDetails의 인스턴스로 사용됩니다.
credentials 비밀번호로 사요됩니다. 사용자가 인증된 후에는 비밀번호가 지워집니다.
authorities 사용자에게 부여된 권한들입니다. GrantedAuthority의 List형식으로 되어있습니다.

 

GrantedAuthority

주체에게 부여된 권한입니다. "ROLE_ADNATIOR", "ROLE_HR_SUPERVISOR" 과 같은 '역할'로 사용됩니다.

사용자 아이디/비밀번호 기반 인증을 사용할 때, UserDetailsService에 의해 로드됩니다.

 

 

3-4. AuthenticationManager

AuthenticationManager는 Spring Security의 필터가 인증을 수행하는 방식을 정의하는 인터페이스입니다.

AuthenticationManager는 인터페이스이므로 따로 구현할 수 있지만 기본적으로 ProviderManager가 구현되어있습니다.

 

 

3-5. AuthenticationProvider

실제 인증에 대한 부분을 담당합니다. 인증전의 Authentication 객체를 받아서 인증이 완료된 Authentication 객체를 반환합니다.

 

 

3-6. UserDetailService

UserDetailService를 implements해서 DB 데이터를 주입하여 UserDetails 를 반환하게 합니다.

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

 

 

3-7. UserDetails

인증된 사용자로써 사용될 인터페이스 입니다. implements해서 Custom해야합니다.

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    default boolean isAccountNonExpired() {
        return true;
    }

    default boolean isAccountNonLocked() {
        return true;
    }

    default boolean isCredentialsNonExpired() {
        return true;
    }

    default boolean isEnabled() {
        return true;
    }
}

 

 

4. Authorization (인가)

인증된 정보를 가지고 접근에 대한 허용여부를 결정하는 과정입니다. GrantedAuthority에 String getAuthority() 메소드가 사용됩니다.

기본적인 규칙으로 'ROLE_' 접두사가 포함됩니다. 즉, "USER"라는 권한이 있는 경우 GrantedAuthority#getAuthority 를 사용하면 "ROLE_USER" 권한을 찾습니다.

 

'ROLE_' 접두사를 변경할 수 있습니다.

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults("MYPREFIX_");
}
// static 필수!!

 

 

 

 

5. 인증처리 과정

 

 

5-1. HTTP 요청

사용자가 로그인 요청을 합니다.

 

5-2 AuthenticationFilter 인증

SecurityFilterChain에서 로그인을 담당하는 UsernamePasswordAuthenticationFilter 에서 인증을 처리합니다.

 

5-3. UsernameAuthenticationToken 발급

아이디와 비밀번호를 통해 토큰을 발급하고 AuthenticationManager로 토큰을 보냅니다.

 

5-4. AuthenticationProvider 인증

인증된 객체를 UserDetailsService에게 넘겨줍니다.

 

5-5. UserDetails 생성

전달된 인증된 객체를 DB에서 조회하여 데이터를 UserDetails 반환

 

5-6. AuthenticationProvider

인증에 성공하면 AuthenticationProvider에서 AuthenticationManager에게 인증에 성공한 객체를 반환

 

5-7. AuthenticationManager

인증에 성공한 객체를 AuthentifacionFIlter에 전달

 

5-8. SecurityContextHolder

인증에 성공한 객체는 SecurityContextHolder에 저장

 

 

 

6. 설정

SecurityFilterChain 에 어떤 기능을 사용할지 설정하는 부분입니다. 이 부분에서는 Filter의 기능을 사용자의 API에 맞게 수정할 수 있고, 해당 Filter의 전과 후에 Custom Filter를 넣는 등 여러 기능을 설정할 수 있습니다.

 

6-1. 기본설정

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        http.csrf(Customizer.withDefaults())
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults())
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            );

        return http.build();
    }

}

 

메소드 필터 설명
csrf CsrfFilter csrf에 대한 설정
httpBasic BasicAuthenticationFilter HTTP Basic Authentication에 대한 설정
formLogin UsernamePasswordAuthenticationFilter form login에 대한 설정
authorizeHttpRequests AuthorizationFilter 권한에 대한 설정

 

위의 4개의 메소드뿐만아니라 수많은 filter에 대한 설정을 할 수 있습니다.

 

Customizer.withDefaults() 는 Security 기본설정에 따른다는 메소드입니다.

 

CSRF에 대해서 이해하기

 

[CS][Spring Security] CSRF란?

CSRF란?CSRF Cross-Site Request Forgery의 약자로 인증된 사용자의 권한을 악용하여 해당 사용자가 의도하지 않은 요청을 웹사이트에 전송하는 공격 기법입니다. 공격자는 사용자가 이미 인증된 상태를

tmd8633.tistory.com

 

 

6-2. Filter 추가

http.

// atFilter를 filter로 변경
addFilterAt(Filter filter, Class<? extends Filter> atFilter)

// beforeFilter 전에 filter를 추가
addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter)

// afterFilter 후에 filter를 추가
addFilterAfter(Filter filter, Class<? extends Filter> afterFilter)
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ...
        .addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class); 
    return http.build();
}

 

 

Filter를 Bean으로 등록

Filter를 Bean으로 선언하면 Spring Container에 등록되면서 Filter가 2번 호출 될 수 있습니다. 이를 방지하기 위해 setEnabled(false) 로 설정해주어야 합니다.

 

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
    FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false);
    return registration;
}

이렇게 하면 HttpSecurity 에 추가한 것만 유효하게 됩니다.

 

 

 

참고자료

공식문서 : https://docs.spring.io/spring-security/reference/servlet/architecture.html

 

+ Recent posts