Spring Security ‐ 인증 프로세스 이해 - dnwls16071/Backend_Summary GitHub Wiki

📚 인증 프로세스

[폼 인증 - formLogin() & UsernamePasswordAuthenticationFilter]

  • HTTP 기반의 폼 로그인 인증 메커니즘을 활성화하는 API로 사용자 인증을 위한 사용자 정의 로그인 페이지를 쉽게 구현할 수 있다.
  • 기본적으로 스프링 시큐리티가 제공하는 기본 로그인 페이지를 사용하며 사용자 이름과 패스워드가 포함된 간단한 양식을 제공한다.
  • 사용자는 웹 폼을 통해 자격 증명(Credentials)을 제공하고 Spring Security는 HttpServletRequest에서 값을 읽어온다.
  • FormLoginConfigurer 설정 클래스를 통해 여러 API들을 설정할 수 있다.
  • 기본적으로 UsernamePasswordAuthenticationFilter를 사용한다.
  • formLogin은 SPA나 API 서버에서는 잘 사용하지 않는다.

  • 스프링 시큐리티는 AbstractAuthenticationProcessingFilter 클래스를 사용해 사용자 자격 증명을 인증하는 기본 필터로 사용한다.
  • UsernamePasswordAuthenticationFilterAbstractAuthenticationProcessingFilter를 확장한 클래스로서 HttpServletRequest에서 제출된 사용자 이름과 비밀번호(자격증명)로부터 인증을 수행한다.
  • 인증 프로세스가 초기화 될 때 로그인 페이지와 로그아웃 페이지 생성을 위한 DefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilter 가 초기화 된다.

[기본 인증 - HttpBasic() & BasicAuthenticationFilter]

  • HTTP는 액세스 제어와 인증을 위한 프레임워크를 제공하며 가장 일반적인 인증 방식은 Basic 인증 방식이다.
  • RFC 7235 표준이며, 인증 프로토콜은 HTTP 인증 헤더에 기술되어 있다.
  1. 클라이언트는 인증 정보 없이 서버에 접속을 시도한다.
  2. 서버가 클라이언트에게 인증하라는 요구를 보낼 때 401 Unauthorized 응답과 함께 WWW-Authentication 헤더를 기술해서 realm(보안 영역)과 Basic 인증 방법을 보낸다.
  3. 클라이언트가 서버로 접속할 떄 Base64로 username과 password를 인코딩하고 Authorization 헤더에 담아서 요청한다.
  4. 성공적으로 완료되면 정상적인 상태 코드를 반환한다.

❗base64 인코딩된 값은 디코딩이 가능하기 때문에 인증 정보가 노출된다. ❗HTTP basic 인증은 반드시 HTTPS와 같이 암호화 기술이 함께 사용되어야만 한다.


  • BasicAuthenticationFilter 필터는 기본 인증 서비스를 제공하는데 사용된다.
  • BasicAuthenticationConverter를 사용해서 요청 헤더에 기술된 인증정보 유효성을 체크하며 Base64 인코딩된 username과 password를 추출한다.
  • 인증 이후 세션을 사용하는 경우와 사용하지 않는 경우에 따라 처리 흐름의 차이가 존재한다.
  • 세션을 사용하는 경우 서버 측의 세션 저장소에서 관리되기 때문에 인증 과정을 거치지 않으나 세션을 사용하지 않으면 매 요청마다 인증 과정을 거치게 된다.

[기억하기 인증 - RememberMe() & RememberMeAuthenticationFilter]

  • 사용자가 웹 사이트나 애플리케이션에 로그인할 때 자동으로 인증 정보를 기억하는 기능이다.
  • UsernamePasswordAuthenticationFilter와 함께 사용되며, AbstractAuthenticationProcessingFilter 슈퍼 클래스에서 훅을 통해 구현된다.
  • 인증 성공 시, RememberMeServices.loginSuccess() 메서드가 호출되면서 RememberMe 토큰을 생성하고 쿠키로 전달한다.
  • 인증 실패 시, RememberMeServices.loginFail() 메서드가 호출되면서 쿠키를 제거한다.
  • LogoutFilter와 연계해서 로그아웃 시 쿠키를 지운다.

  • 기본적으로 암호화된 토큰으로 생성되며 브라우저에 쿠키를 보내고 향후 세션에서 이 쿠키를 감지해 자동 로그인이 이루어지는 방식으로 진행된다.
  • base64(username + ":" + expirationTime + ":" + algorithmName + ":" algorithmHex(username + ":" + expirationTime + ":" password + ":" + key))
    • username: UserDetailsService 로 식별 가능한 사용자 이름
    • password: 검색된 UserDetails 에 일치하는 비밀번호
    • expirationTime: remember-me 토큰이 만료되는 날짜와 시간, 밀리초로 표현
    • key: remember-me 토큰의 수정을 방지하기 위한 개인 키
    • algorithmName: remember-me 토큰 서명을 생성하고 검증하는 데 사용되는 알고리즘(기본적으로 SHA-256 알고리즘을 사용)

  • TokenBasedRememberMeServices : 쿠키 기반 토큰 보안을 위해 해싱이 사용된다.
  • PersistentTokenBasedRememberServices : 생성된 토큰을 저장하기 위해 데이터베이스나 다른 영구 저장 매체를 사용한다.
  • 두 구현 모두 사용자 정보를 검색하기 위해 UserDetailsService 인터페이스를 구현해야한다.

  • RememberMeAuthenticationFilterSecurityContextHolder에 Authentication이 포함되지 않은 경우 실행되는 필터이다.
  • 세션이 만료되었거나 애플리케이션 종료로 인해 상태가 소멸된 경우 토큰 기반 인증을 사용해 유효성을 검사하고 토큰이 검증되면 자동 로그인 처리를 수행한다.

[익명 인증 사용자 - Anonymous() & AnonymousAuthenticationFilter]

  • 스프링 시큐리티에서 익명으로 인증된 사용자와 인증되지 않은 사용자 간에 실제 개념적 차이는 없으며 단지 액세스 제어 속성을 구성하는 편리한 방법을 제공한다.
  • SecurityContextHolder가 항상 Authentication 객체를 포함하고 null을 포함하지 않는다는 것을 규칙을 세우게 되면 클래스를 더 견고하게 작성할 수 있다.
  • 인증 사용자와 익명 사용자를 구분해서 어떤 기능을 수행하고자 할 때 유용하게 사용 가능하며 익명 인증 객체를 세션에 저장하지 않는다.
  • 익명 인증 사용자의 권한을 별도로 운용할 수 있다. 인증된 사용자가 접근할 수 없도록 구성한다.

  • 만약 익명 요청에서 Authentication을 얻고 싶다면 @CurrentSecurityContext를 사용하면 된다.
  • CurrentSecurityContextResolver에서 요청을 가로채어 처리한다.

[로그아웃 - logout() & LogoutFilter]

  • 스프링 시큐리티는 기본적으로 DefaultLogoutPageGeneratingFilter를 통해 로그아웃 페이지를 제공해 GET /logout URL로 접근이 가능하다.
  • 로그아웃 실행은 기본적으로 POST /logout으로도 가능하나 CSRF 기능을 비활성화 혹은 RequestMatcher를 사용하면 GET, PUT, DELETE 모두 가능하다.
  • 로그아웃 필터를 거치지 않고 스프링 MVC에서 커스텀 구현도 가능하며 로그인 페이지가 커스텀하게 생성될 경우 로그아웃 기능 역시 커스텀으로 구현해야 한다.

[요청 캐시 - RequestCache / SavedRequest & RequestCacheAwareFilter]

  • RequstCache는 인증 절차 문제로 리다이렉트 된 후에 이전에 했던 요청 정보를 담고 있던 SavedRequest 객체를 쿠키 혹은 세션에 저장하고 필요시 다시 가져와 실행하는 캐시 메커니즘이다.
  • SavedRequest는 로그인과 같은 인증 절차 진행 후 사용자를 인증 이전의 마지막 페이지로 안내하며 이전 요청과 관련된 여러 정보를 저장한다.

❗그러나 만약 인증되지 않은 사용자가 매번 요청을 보내면 위와 같은 흐름이 계속 진행될텐데 불필요한 과정이 될 수가 있다. 그래서 요청을 저장하지 않도록 하려면 NullRequestCache 구현을 사용할 수 있다.

  • RequestCacheAwareFilter는 이전에 저장했던 웹 요청을 다시 불러오는 역할을 한다.
  • SavedRequest가 현재 Request와 일치하면 이 요청을 필터 체인의 doFilter 메서드에 전달하고 SavedRequest가 없으면 필터는 원래 Request를 그대로 진행시킨다.