시냅스

Spring Security 공식 문서 기반 정리 본문

Java, Spring/Security

Spring Security 공식 문서 기반 정리

ted k 2022. 9. 30. 21:31

Spring Security가 framework로 하는 일

  • ServletContext 내부로 가지 않고 Filter 수준에서 보안을 설정한다.
  • 어플리케이션의 모든 상호작용에 사용자 인증 요구
  • 디폴트 로그인 폼 생성
  • user 라는 이름과 콘솔에 출력한 비밀번호를 사용한 폼 기반 인증 지원
  • BCrypt로 저장할 비밀번호 보호
  • 사용자 로그아웃 지원
  • CSRF 공격 방어
  • Session Fixation 방어
  • 보안 헤더 통합
    • HTTP Strict Transport Security로 요청을 보호
    • X-Content-Type-Options 통합
    • Cache Control (어플리케이션에서 특정 스태틱 리소스에 캐시를 허용하도록 재정의할 수 있다.)
    • X-XSS-Protection 통합
    • X-Frame-Options 통합으로 클릭재킹 방어 지원
  • 서블릿 API 메소드 통합
    • HttpServletRequest#getRemoteUser()
    • HttpServletRequest.html#getUserPrincipal()
    • HttpServletRequest.html#isUserInRole(java.lang.String)
    • HttpServletRequest.html#login(java.lang.String, java.lang.String)
    • HttpServletRequest.html#logout()

 

서블릿 Filter 와 Spring Security

 

  • 스프링 부트는 DelegatingFilterProxy 라는 filter 구현체로 서블릿 컨테이너의 생명주기와 스프링의 ApplicationContext를 연결한다.
  • 서블릿 컨테이너는 자체 표준을 사용해서 Filter를 등록할 수 있지만 스프링이 정의하는 빈은 인식하지 못한다.
  • DelegatingFilterProxy는 표준 서블릿 컨테이너 메커니즘으로 등록할 수 있으면서도, 모든 처리를 Filter를 구현한 스프링 빈으로 위임한다.
  • 위의 figure는 DelegatingFilterProxy가 어떻게 여러 Filter로 구성된 FilterChain에 껴들어 가는지 보여준다.

 

 

  • 스프링 시큐리티는 FilterChainProxy로 서블릿을 지원한다.
  • FilterChainProxy는 스프링 시큐리티가 제공하는 특별한 Filter로 SecurityFilterChain을 통해 여러 Filter 인스턴스로 위임할 수 있다.
  • FilterChainProxy는 보통 빈이기 때문에 DelegatingFilterProxy로 감싸져 있다.
  • SecurityFilterChain에 있는 보안 필터들은 전형적인 빈이지만 DelegatingFilterProxy가 아닌 FilterChainProxy로 등록한다.
    • 스프링 시큐리티가 서블릿을 지원할 수 있는 시작점이 되기 위해
    • SecurityFilterChain 을 어떨 때 실행할지 유연하게 결정하기 위해
      • 실행여부를 HttpServletRequest를 통해 결정할 수 있다.
  • 아래는 스프링 시큐리티 필터 목록이다.
    • HeaderWriterFilter : Http 해더를 검사한다. 써야 할 건 잘 써있는지, 필요한 해더를 더해줘야 할 건 없는가?
    • CorsFilter : 허가된 사이트나 클라이언트의 요청인가?
    • CsrfFilter : 내가 내보낸 리소스에서 올라온 요청인가?
    • LogoutFilter : 지금 로그아웃하겠다고 하는건가?
    • UsernamePasswordAuthenticationFilter : username / password 로 로그인을 하려고 하는가? 만약 로그인이면 여기서 처리하고 가야 할 페이지로 보내 줄께.
    • ConcurrentSessionFilter : 여거저기서 로그인 하는걸 허용할 것인가?
    • BearerTokenAuthenticationFilter : Authorization 해더에 Bearer 토큰이 오면 인증 처리 해줄께.
    • BasicAuthenticationFilter : Authorization 해더에 Basic 토큰을 주면 검사해서 인증처리 해줄께.
    • RequestCacheAwareFilter : 방금 요청한 request 이력이 다음에 필요할 수 있으니 캐시에 담아놓을께.
    • SecurityContextHolderAwareRequestFilter : 보안 관련 Servlet 3 스펙을 지원하기 위한 필터라고 한다.(?)
    • RememberMeAuthenticationFilter : 아직 Authentication 인증이 안된 경우라면 RememberMe 쿠키를 검사해서 인증 처리해줄께
    • AnonymousAuthenticationFilter : 아직도 인증이 안되었으면 너는 Anonymous 사용자야
    • SessionManagementFilter : 서버에서 지정한 세션정책을 검사할께.
    • ExcpetionTranslationFilter : 나 이후에 인증이나 권한 예외가 발생하면 내가 잡아서 처리해 줄께.
    • FilterSecurityInterceptor : 여기까지 살아서 왔다면 인증이 있다는 거니, 니가 들어가려고 하는 request 에 들어갈 자격이 있는지 그리고 리턴한 결과를 너에게 보내줘도 되는건지 마지막으로 내가 점검해 줄께.
    • 그 밖에... OAuth2 나 Saml2, Cas, X509 등에 관한 필터들도 있습니다.

 

 

 

 

AccessDeniedHandler

  • ExceptionTranslationFilter는 AccessDeniedException을 해석하고 AuthenticationException을 Http 응답으로 바꿔준다.
  1. 먼저 ExceptionTranslationFilter는 FilterChain.doFilter(request, response)를 호출해서 어플리케이션의 나머지 로직을 실행한다.
  2. 인증받지 않은 사용자였거나 AuthenticationException이 발생했다면 인증을 시작한다.
    1. SecurityContextHolder를 비운다.
    2. requestCache에 HttpServletRequest를 저장한다. 사용자 인증에 성공하면 RequestCache로 기존 요청 처리를 이어간다.
    3. AuthenticationEntryPoint는 클라이언트에 credential을 요청할 때 사용한다.
  3. 반대로 AccessDeniedException 이었다면 접근을 거부한다. 거절된 요청은 AccessDeniedHandler에서 처리한다.

 

Spring Security Authentication - Architecture Componenets

  • SecurityContextHolder
    • 스프링 시큐리티에서 인증한 대상에 대한 상세 정보
    • SecurityContext
      • 현재 인증한 사용자의 Authentication을 가지고 있다.
  • Example. Setting SecurityContextHolder
SecurityContext context = SecurityContextHolder.createEmptyContext(); // (1)
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); // (2)
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); // (3)
  1. 비어있는 SescurityContext를 만든다.
  2. Authentication 객체를 만든다.
    1. 실제로는 TestingAuthenticationToken 대신 UsernamePasswordAuthenticationToken(userDetails, password, authorities) 를 주로 사용한다.
  3. SecurityContextHolder에 SecurityContext를 설정한다.
  • Example. Access Currently Authenticated User
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  • SecurityContextHolder는 ThreadLocal을 사용해 정보를 저장하기 때문에 메소드에 직접 SecurityContext를 넘기지 않아도 동일한 스레드라면 SecurityContext에 접근할 수 있다.
  • 스프링 시큐리티의 FilterChainProxy는 항상 SecurityContext를 비워준다.

 

  • Authentication
    • 현재 인증된 결과
    • 인증하기 위한 정보, 인증을 받기 위한 정보
    • 객체로 관리한다.
    • credentials
      • 인증을 받기 위해 필요한 정보, 비밀번호 등 (input, AuthenticationProvider에서의 input)
    • Principal
      • 인증된 결과, 인증 대상 (output, AuthenticationProvider에서의 output)
      • 보통 UserDetails 인스턴스다.
    • Details
      • 기타 정보, 인증에 관여된 주변 정보들
    • Authorities
      • 권한 정보들
    • Authentication을 구현한 객체들은 token 이라는 이름의 객체로 구현된다. 따라서 구현체를 인증 토큰이라고 불러도 좋다.
    • Authentication 객체는 SecurityContextHolder를 통해 세션이 없어도 언제든 접근할 수 있도록 필터 체인에서 보장한다.
  • GrantedAuthority
    • Authentication에서 접근 주체(principal)에 부여한 권한
    • Authentication.getAuthorities() 메소드로 접근할 수 있다.

 

  • AuthenticationManager
    • 인증 제공자들을 관리하는 인터페이스
    • 구현 객체는 ProviderManager
      • 복수개 존재할 수 있다.
      • 실제 수행은 AuthenticationProvider에서 한다
  • AuthenticationProvider
    • Authentication을 받아서 인증하고 인증된 결과를 다시 Authentication 객체로 전달한다.
    • 인증 대상과 방식이 다양할 수 있기 때문에 인증 제공자도 여러개 올 수 있다

 

Form Login

 

  • html 폼 기반 사용자 이름/비밀번호 인증
  1. 사용자가 권한이 없는 리소스 /private 에 인증되지 않은 요청을 보낸다.
  2. 스프링 시큐리티의 FilterSecurityInterceptor에서 AccessDeniedException을 던져 인증되지 않은 요청을 거절했음을 알린다.
  3. 인증되지 않은 사용자이므로 ExceptionTranslationFilter에서 인증을 시작하고, 설정한 AuthenticationEntryPoint로 로그인 페이지로의 리다이렉트 응답을 전송한다. 
  4. 그러면 브라우저는 리다이렉트된 로그인 페이지를 요청한다.
  5. 어플리케이션에서 로그인 페이지를 렌더링해야 한다.

 

  • DaoAuthenticationProvider
    • UserDetailsService와 PasswordEncoder로 username/password를 인증하는 AuthenticationProvider 구현체
  1. UsernamePasswordAuthenticationToken을 ProviderManager로 넘긴다.
  2. ProviderManager는 DaoAuthenticationProvider를 사용하도록 설정 돼 있다.
  3. DaoAuthenticationProvider는 UserDetailsService에서 UserDetails를 조회한다.
  4. DaoAuthenticationProvider는 이전 단계에서 얻은 UserDetails에 있는 비밀번호를 PasswordEncoder로 검증한다.
  5. 인증에 성공하면 UsernamePasswordAuthenticationToken 타입의 Authentication을 반환하며, 이 객체는 UserDetailsService가 리턴한 UserDetails를 Principal로 가지고 있다.
  6. UsernamePasswordAuthenticationToken 는 인증 Filter에서 SecurityContextHolder로 세팅된다.
  • UserDetails
    • UserDetailService가 리턴하는 값
    • DaoAuthenticationProvider가 Userdetails를 인증하고, UserDetails를 principal로 가진 Authentication을 리턴한다. 
    • UserDetailService와 UserDetails 구현체를 구현하면 스프링 시큐리티가 나머지는 쉽게 쓸 수 있도록 도와줌
  • UserDetailsService
    • DaoAuthenticationProvider가 username/password로 인증할 때 필요한 username, password와 다른 속성을 조회할 때 사용한다.
    • 커스텀 인증을 정의하려면 커스텀 UserDetailsService를 빈으로 만들면 된다.
  • PasswordEncoder
    • 서블릿에서 스프링 시큐리티를 사용하려면 PasswordEncoder를 통합해 비밀번호를 안전하게 저장할 수 있다.
Comments