스프링 시큐리티 (Spring Security) - 구조, 동작, 기능, 인증 방식.

하쮸·2025년 1월 3일
post-thumbnail

1. 버전 차이.


2. 스프링 시큐리티

  • 스프링 시큐리티는 스프링 기반 애플리케이션의 인증(Authentication)인가(Authorization)를 담당하는 프레임워크.

    • 애플리케이션의 보안성을 높이고 사용자 인증, 접근 제어, 권한 관리 등을 구현할 수 있음.
  • 스프링 시큐리티는 다양한 인증 방식과 보안 기능을 제공하고 커스터마이징이 가능해서 다양한 요구사항을 충족시킬 수 있음.

  • 스프링 시큐리티는 스프링 프레임워크에 대하여 인증, 권한 부여 등의 기능을 제공함.

  • 특징

    • 스프링 내에서 필터를 기반으로 동작함.
    • 즉, Http Request가 Dispatcher Servlet에 도착하기 이전에 스프링 시큐리티가 제공하는 기능을 활용하여, 인증 or 인가에 대한 사전 검증을 진행함.

2-1. 구조.

  • AuthenticationFilter

    • 스프링 시큐리티 필터 체인에서 인증 요청처리하는 역할.
    • 사용자가 인증(일반적으로 사용자 이름 및 비밀번호)요청하면 이 필터가 사용자를 인증함.
  • UsernamePasswordAuthenticationToken

    • 사용자 이름과 비밀번호를 포함하는 Authentication의 구체적인 구현체임.

    • 일반적으로 폼 기반 로그인에서 사용됨.

    • public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
          private static final long serialVersionUID = 620L;
          private final Object principal;
          private Object credentials;
      
          public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
              super((Collection)null);
              this.principal = principal;
              this.credentials = credentials;
              this.setAuthenticated(false);
          }
      
          public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
              super(authorities);
              this.principal = principal;
              this.credentials = credentials;
              super.setAuthenticated(true);
          }
      
          public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
              return new UsernamePasswordAuthenticationToken(principal, credentials);
          }
      
          public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
              return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
          }
      
          public Object getCredentials() {
              return this.credentials;
          }
      
          public Object getPrincipal() {
              return this.principal;
          }
      
          public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
              Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
              super.setAuthenticated(false);
          }
      
          public void eraseCredentials() {
              super.eraseCredentials();
              this.credentials = null;
          }
      }
    • UsernamePasswordAuthenticationToken은 Authentication을 implements한 AbstractAuthenticationToken의 자식 클래스.

    • User의 IDPrincipal이 담당하고, PasswordCredential이 담당함.

    • UsernamePasswordAuthenticationToken의 첫 번째 생성자인증 전의 객체를 생성하고, 두번째 생성자인증이 완료된 객체를 생성.

  • AuthenticationManager

    • 실제로 사용자를 인증하는 인터페이스.
    • 주어진 Authentication 객체를 기반으로 사용자를 인증하고 Authentication 객체를 반환함.
  • ProviderManager

    • 기본 AuthenticationManager 구현체 중 하나로서, 여러 개의 AuthenticationProvider를 관리하고 이를 사용하여 사용자를 인증함.
  • AuthenticationProvider

    • 실제로 인증을 수행하는 객체.
    • 사용자가 요청한 인증을 검증하고 Authentication 객체를 반환함.
    • 스프링 시큐리티에서는 다양한 인증 provider를 지원하고 그 중 대표적으로 DaoAuthenticationProvider, LdapAuthenticationProvider, OpenIDAuthenticationProvider 등이 있음.
  • UserDetailsService

    • 사용자 정보를 가져오는 인터페이스.
    • 사용자의 식별자(일반적으로 사용자 이름)를 기반으로 사용자 정보를 가져옴.
  • UserDetails

    • 사용자의 정보를 담는 인터페이스.
    • 사용자 이름, 비밀번호, 권한 등의 정보를 포함함.
    • 사용자 정보를 UserDetailsService로부터 가져올 때 사용됨.
  • User

    • 스프링 시큐리티에서 기본적으로 제공하는 UserDetails의 구현 클래스 중 하나.
    • 사용자의 기본 정보를 저장하고 사용자 이름, 비밀번호, 권한 등을 포함함.
  • SecurityContextHolder

    • 현재 사용자의 SecurityContext를 관리하는 유틸리티 클래스로서 현재 사용자의 Authentication 객체를 저장하고 관리함.
  • SecurityContext

    • 현재 사용자의 보안 정보를 저장하는 컨테이너.
    • Authentication을 보관하는 역할.
    • Authentication 객체를 꺼내올 수 있음.
    • SecurityContextHolder를 사용하여 현재 사용자의 SecurityContext를 설정하고 얻을 수 있음.
  • Authentication 클래스

    • 스프링 시큐리티에서 사용자의 인증 정보와 권한 정보를 포함하는 클래스.
    • Authentication 객체를 통해 현재 사용자의 인증 상태 및 권한을 확인할 수 있음.
    • Authentication 객체는 Security Context에 저장됨.
    • SecurityContextHolder를 통해 SecurityContext에 접근하고 SecurityContext를 통해 Authentication에 접근.

2-2. 요청 흐름.

  • 사용자가 웹 애플리케이션에 로그인을 요청.
  • 로그인 폼에서 사용자가 사용자 이름과 비밀번호를 보내면 이 정보는 Spring Security AuthenticationFilter전달됨.
  • AuthenticationFilterUsernamePasswordAuthenticationToken생성한 뒤 사용자 이름과 비밀번호를 이 토큰포함시킴.
  • 해당 토큰AuthenticationManager전달됨.
    AuthenticationManager는 ProviderManager를 포함하고 다양한 AuthenticationProvider관리함.
  • AuthenticationManager는 등록된 AuthenticationProvider들을 돌아다니면서 토큰검증하는 시도를 함.
    각 AuthenticationProvider는 다양한 방식으로 사용자를 인증할 수 있음.
    일반적으로 사용자 이름/비밀번호를 검증하는 DaoAuthenticationProvider를 사용함.
  • DaoAuthenticationProvider는 사용자 이름을 기반으로 UserDetailsService를 호출하여 데이터베이스나 다른 저장소에서 사용자 정보를 가져옴.
    UserDetailsService는 해당 사용자 이름에 맞는 사용자 정보를 찾고, UserDetails 객체를 생성하여 반환함.
    DaoAuthenticationProvider는 반환된 UserDetails 객체를 사용하여 비밀번호 검증을 수행.
    만약 사용자가 검증을 통과하면, UserDetails 객체에 포함된 사용자 정보와 권한 정보를 기반으로 Authentication 객체를 생성하고 반환함.
  • AuthenticationManager는 검증된 Authentication 객체를 다시 AuthenticationFilter로 반환.
  • AuthenticationFilter는 사용자의 Authentication 객체SecurityContextHolder저장함.
    이렇게 해서 현재 사용자의 보안 컨텍스트가 설정됨.
  • 사용자가 인증된 후, Spring Security는 요청을 계속 진행시킴.
    사용자가 해당 리소스에 대한 권한이 있는지 확인하기 위해서 권한 부여(Authorization) 단계로 이동하게 됨.
  • 요청된 리소스에 액세스할 때, Spring Security는 @PreAuthorize, @PostAuthorize, @Secured 등과 같은 에노테이션을 사용해서 메서드 레벨에서 권한을 확인하고 사용자에게 액세스 권한이 있는지 확인함.
  • 요청된 작업이 사용자에게 허용된 경우, 리소스에 대한 요청이 성공하고 응답이 클라이언트에게 반환됨.
    • 만약 권한이 없는 경우, 액세스가 거부되고 클라이언트는 적절한 오류 메시지나 리다이렉션을 받게 됨.

  • 스프링부트 애플리케이션은 톰캣 서블릿 컨테이너 위에 존재함.
    • 그래서 클라이언트의 요청이 들어오면 먼저 서블릿 컨테이너가 요청을 받아서 서블릿 컨테이너에 존재하는 필터들을 통과한 후에 스프링 부트 컨트롤러에 그 요청이 도달하게 됨.
  • 근데 스프링 시큐리티 config 파일을 등록해놓으면 config 파일이 필터에 특정한 필터를 만들어서 클라이언트의 요청을 요청을 가로챔.
    • 가로챈 후에 이 요청의 목적지 이전에서 해당 클라이언트가 특정한 권한이나 적절한 권한을 가지고 있는 확인을 해서 있다면 통과, 없다면 필터에서 막게됨.

2-3. 주요 클래스, 인터페이스.

  • UserDetails

    • 스프링 시큐리티에서 사용자의 정보를 기반으로, 인증, 인가 기능을 제공하는 인터페이스.

    • 현재 서비스에서 사용하고 있는 User 클래스에 implements(구현)해서 기존 클래스에 인증, 인가 관련 기능을 부여함.

    • 해당 클래스 인스턴스를 기반으로 스프링 서버 내에서 인증, 인가 작업을 수행하는 것.

    • 만약 실무에 사용하는 경우

      • 서비스에 따라 추가적인 사항이 더 필요한 경우가 많아서 UserDetail인터페이스를 상속받는 커스텀 UserDetail 클래스를 만들어 사용하는 것이 관례.
    • getAuthorities() : 계정의 권한 목록을 리턴.

    • getPassword() : 계정의 비밀번호를 리턴.

    • getUsername() : 계정의 고유한 값을 리턴. (중복 없는 ID(username), PK)

    • isAccountNonExpired() : 계정의 만료 여부를 리턴.

    • isAccountNonLocked() : 계정의 잠김 여부를 리턴.

    • isEnabled() : 계정의 활성화 여부를 리턴.

  • UserDetailsService

    • 스프링 시큐리티의 인증과정 중 실제 사용자 정보를 조회하기 위한 서비스.
    • UserDetails 객체를 생성하는loadUserByUsername() 메서드를 가지고 있음.
      • loadUserByUsername(String username)
        • 사용자의 username에서 claim 정보를 기반으로, 사용자를 조회하여, 조회된 정보를 기반으로 UserDetails 인스턴스를 생성.
  • Authentication

    • 현재 접근하려는 사용자의 정보와 권한을 담는 인터페이스.
  • AuthenticationProvider

    • 인증에 대한 Provider 클래스를 생성 시에 사용되는 인터페이스.
    • 이를 implements(구현) 하는 경우 authenticate()와 supports() 메서드를 오버라이드 함.
  • authenticate()

    • 실제 인증 로직 구현이 이루어지는 메서드.
    • 관련 로직 수행 후, Authentication 객체를 반환하도록 설정되어 있음.
  • supports()

    • 현재 Provider 클래스가 특정 타입의 Authentication 객체를 지원하는 여부를 알려주는 메서드.
    • 만약 Provider 가 여러개인 경우.
      • AuthenticationManager 내의 등록된 Provider 가운데, 주어진 Authentication 객체를 지원하는 지를 확인하는 용도로 사용할 수 있음.
  • AuthenticationManager

    • AuthenticationProvider 구현체를 관리하는 클래스.
    • AuthenticationManager 해당 구현체를 등록하는 것으로 추후, 상황에 따라 필요한 AuthenticationProvider 클래스를 AuthenticationManager 를 통해 불러오는 것이 가능.
  • SecurityContext

    • Authentication 정보를 서버 내에서 보관하는 역할을 함.
    • 향후 인증이 필요한 요청에 대해 @AuthenticationPrincipal 어노테이션을 부여하는 것으로, SecurityContext 내의 Authentication 내의 사용자 정보를 가져오는 것이 가능함.
  • UsernamePasswordAuthenticationToken

    • SpringSecurity 는 Username 과 Password 를 기반으로 인증 및 권한 부여 정보를 설정하는 방식을 채택.
    • 이 때 사용자에 대한 권한 부여 정보를 가진 클래스가 바로 Authentincation임.
    • UserDetails 클래스를 기반으로, Authentication 객체를 반환하는 클래스가 UsernamePasswordAuthenticationToken임.
  • GrandAuthority

    • 현재 사용자가 가지고 있는 권한을 의미.
    • ROLE_XX 등의 형태로 사용됨.
    • 특정 자원에 대한 접근 시, 권한 여부를 확인하는데 사용되는 객체.

2-4. 기능.

  • 인증 (Authentication)
    • 인증은 등록되어 있는 사용자인지 확인하는 과정.
    • 아이디와 패스워드를 이용해서 로그인을 통해 인증
  • 인가 (Authorization)
    • 인가는 인증된 사용자의 권한을 확인해서 권한에 따라 리소스의 사용범위를 구분해서 허락해 주는 것.
  • 사용자 지정 필터 및 이벤트 핸들러
  • 자동 로그인 (Remember-Me)
  • 세션 관리
  • 암호화 및 비밀번호 관리
  • CSRF 방어(Cross-Site Request Forgery)
  • CORS 설정(Cross-Origin Resource Sharing)
  • Method Security
  • Security Headers 설정
  • 로그인 실패 및 성공 처리
    ...

2-5. 인증 방식

  • 인메모리 인증 방식
    • 사용자 정보를 메모리에 저장하여 인증하는 방식.
    • 간단하고 빠르게 설정할 수 있음.
      • 테스트 환경이나 작은 애플리케이션에서 유용함.
    • 서버가 재시작되면 사용자 정보가 사라지고, 대규모 애플리케이션에는 적합하지 않음.
  • JDBC 인증 방식
    • 데이터베이스에 저장된 사용자 정보를 기반으로 해서 인증하는 방식.
    • 데이터베이스와 연동하여 사용자 인증을 처리할 수 있음.
    • 데이터베이스에 저장된 정보를 기반으로 운영되므로 성능이나 데이터베이스 보안에 신경 써야함.
    • 대규모 애플리케이션에 적합.
  • 사용자 정의 인증 방식
    • 개발자가 직접 구현한 인증 로직을 사용하는 방식.
    • 특정 요구사항에 맞춘 세부적인 인증 방식을 구현할 수 있어서 유연성이 높음.
    • 구현이 복잡해질 수 있고 인증 로직을 직접 처리해야 하므로 유지보수에 더 많은 시간이 필요해짐.

3. 서블릿 필터와 Spring Security

  • 클라이언트에서 요청을 보내게 되면
    Client(request) -> Filter -> DispatcherServlet -> Interceptor -> Controller 순으로 처리됨.
    Filter는 DispatcherServlet 전에 적용되어 가장 먼저 URL 요청을 받음.

  • 스프링 시큐리티는 서블릿(Servlet) 필터를 기반으로 인증과 인가에 대한 부분을 Filter 흐름에 따라 처리함.
    • 서블릿 필터는 서블릿의 전처리나 후처리 작업을 할 수 있음.
  • But 스프링 빈(Bean)은 스프링 컨테이너에서 생성 및 관리하는 컴포넌트들이고,
    ServletFilter는 서블릿 컨테이너에서 생성 및 관리하는 컴포넌트들임.
    • 즉, 서로 실행되는 위치가 다르기 때문에 ServletFilter는 스프링 빈(Bean)을 주입해서 사용할 수 없음.

  • 그래서 스프링 프레임워크에서는 서블릿 필터에 DelegatingFilterProxy라는 구현체를 제공하고, 해당 필터를 등록하면 직접 처리하지 않고 스프링 빈에 요청을 위임함.
    • DelegatingFilterProxy에서 Delegate request를 보내고, 스프링 시큐리티에서 제공하는 FilterChainProxy가 요청을 위임받아 서블릿 필터들을 스프링 IoC에 등록함.
      • 이를 통해 스프링 IoC 컨테이너가 서블릿 필터를 스프링 빈(Bean)으로 사용할 수 있게 됨.
  • FilterChainProxy는 SecurityFilterChain이라는 이름의 스프링 빈을 찾아서 각 필터들을 순서대로 호출하며 인증, 인가 및 각종 요청에 대한 처리를 수행.
    • SecurityFilterChain은 여러 필터들로 구성되어 있는데, 고유한 설정을 가진 여러 개의 SecurityFilterChain을 두고 URL마다 시큐리티 필터를 맵핑할 수 있음.

4. 참고.

profile
Every cloud has a silver lining.

0개의 댓글