인증 프로세스
Spring Security의 다양한 인증 메커니즘과 필터 동작 원리
목차
formLogin), HTTP Basic 인증(httpBasic), 기억하기 인증(rememberMe), 익명 사용자(anonymous), 로그아웃(logout), 그리고 요청 캐시(RequestCache) 메커니즘의 동작 원리와 설정 방법을 다룬다.
1. 폼 인증 - formLogin()
개념
폼 인증은 HTTP 기반의 폼 로그인 인증 메커니즘을 활성화하는 API로서, 사용자 정의 로그인 페이지를 쉽게 구현할 수 있다. 기본적으로 Spring Security가 제공하는 기본 로그인 페이지를 사용하며, 사용자 이름과 비밀번호 필드가 포함된 간단한 로그인 양식을 제공한다.
사용자는 웹 폼을 통해 자격 증명(사용자 이름과 비밀번호)을 제출하고, Spring Security는 HttpServletRequest에서 이 값을 읽어 인증을 수행한다.
폼 인증 흐름
인증되지 않은 사용자가 보호된 자원에 접근하면 다음과 같은 과정을 거친다.
formLogin() API
FormLoginConfigurer 설정 클래스를 통해 다양한 API를 설정할 수 있으며, 내부적으로 UsernamePasswordAuthenticationFilter가 생성되어 폼 방식의 인증 처리를 담당한다.
HttpSecurity.formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer
.loginPage("/loginPage") // 사용자 정의 로그인 페이지, 기본 로그인페이지 무시
.loginProcessingUrl("/loginProc") // 사용자 이름과 비밀번호를 검증할 URL (Form action)
.defaultSuccessUrl("/", alwaysUse) // 로그인 성공 이후 이동 페이지
.failureUrl("/failed") // 인증 실패 시 보내질 URL
.usernameParameter("username") // 사용자 이름 HTTP 매개변수 설정
.passwordParameter("password") // 비밀번호 HTTP 매개변수 설정
.failureHandler(AuthenticationFailureHandler) // 인증 실패 핸들러
.successHandler(AuthenticationSuccessHandler) // 인증 성공 핸들러
.permitAll() // 로그인 관련 URL 모든 사용자 접근 허용
);
defaultSuccessUrl(url, alwaysUse):alwaysUse가true이면 무조건 지정된 위치로 이동.false(기본값)이면 인증 전 방문 페이지로 리다이렉트.failureHandler를 지정하면failureUrl설정은 무시된다.successHandler를 지정하면defaultSuccessUrl설정은 무시된다. 기본값은SavedRequestAwareAuthenticationSuccessHandler.
2. 폼 인증 필터 - UsernamePasswordAuthenticationFilter
개념
Spring Security는 AbstractAuthenticationProcessingFilter 클래스를 사용자의 자격 증명을 인증하는 기본 필터로 사용한다. UsernamePasswordAuthenticationFilter는 이 클래스를 확장한 구현체로서, HttpServletRequest에서 제출된 사용자 이름과 비밀번호로부터 인증을 수행한다.
인증 프로세스가 초기화될 때 DefaultLoginPageGeneratingFilter 및 DefaultLogoutPageGeneratingFilter가 함께 초기화된다.
동작 흐름
POST /login"]:::client --> B["UsernamePasswordAuthenticationFilter"]:::filter B --> C{"RequestMatcher
매칭 확인"} C -->|N| D["chain.doFilter()"]:::skip C -->|Y| E["Token 생성
UsernamePasswordAuthenticationToken
━━━━━━━━━━━━━━━━━━
principal = 'user'
credentials = '1111'
authenticated = false"]:::token_before E --> F["AuthenticationManager"]:::manager F --> G{"인증 성공?"} G -->|"실패"| H["SecurityContextHolder 삭제"]:::fail H --> I["RememberMeServices.loginFail()"]:::fail I --> J["AuthenticationFailureHandler"]:::fail G -->|"성공"| K["Token 재생성
UsernamePasswordAuthenticationToken
━━━━━━━━━━━━━━━━━━
principal = User('user')
credentials = null
authorities = ROLE_USER
authenticated = true"]:::token_after K --> L["SessionAuthenticationStrategy"]:::success L --> M["SecurityContextHolder 저장"]:::success M --> N["RememberMeServices.loginSuccess()"]:::success N --> O["ApplicationEventPublisher"]:::success O --> P["AuthenticationSuccessHandler"]:::success classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef token_before fill:#5c3d00,stroke:#d29922,color:#ffffff classDef token_after fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef manager fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef fail fill:#5c1a1a,stroke:#f85149,color:#ffffff classDef success fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef skip fill:#2d333b,stroke:#768390,color:#e6edf3
인증 전후 토큰 비교
3. 기본 인증 - httpBasic()
개념
HTTP Basic 인증은 HTTP 액세스 제어와 인증을 위한 프레임워크에서 가장 일반적인 인증 방식이다. RFC 7235 표준이며, 인증 프로토콜은 HTTP 인증 헤더에 기술되어 있다.
- 클라이언트는 인증정보 없이 서버로 접속을 시도한다
- 서버가
401 Unauthorized응답과 함께WWW-Authenticate헤더로 realm과 Basic 인증방법을 보낸다 - 클라이언트가 Base64로 username과 password를 인코딩하고
Authorization헤더에 담아서 요청한다 - 성공적으로 완료되면 정상적인 상태 코드를 반환한다
httpBasic() API
HttpSecurity.httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer
.realmName("security") // HTTP 기본 영역을 설정한다
.authenticationEntryPoint(
(request, response, authException) -> {} // 인증 실패 시 호출되는 EntryPoint
// 기본값: BasicAuthenticationEntryPoint
)
);
4. 기본 인증 필터 - BasicAuthenticationFilter
개념
BasicAuthenticationFilter는 기본 인증 서비스를 제공하는 필터이다. BasicAuthenticationConverter를 사용해서 요청 헤더에 기술된 인증정보의 유효성을 체크하며, Base64 인코딩된 username과 password를 추출한다.
세션을 사용하는 경우 매 요청마다 인증과정을 거치지 않으나, 세션을 사용하지 않는 경우 매 요청마다 인증과정을 거쳐야 한다.
동작 흐름
GET /home
Authorization: Basic dXNlcjoxMTEx"]:::client --> B["BasicAuthenticationFilter"]:::filter B --> C["BasicAuthenticationConverter
━━━━━━━━━━━━━━━━━━
Base64 디코딩
'dXNlcjoxMTEx' → 'user:1111'"]:::converter C --> D["Token 생성
UsernamePasswordAuthenticationToken
━━━━━━━━━━━━━━━━━━
principal = 'user'
credentials = '1111'"]:::token_before D --> E["AuthenticationManager"]:::manager E --> F{"인증 성공?"} F -->|"실패"| G["SecurityContextHolder 삭제"]:::fail G --> H["AuthenticationEntryPoint
WWW-Authenticate 헤더 전송
401 Unauthorized"]:::fail F -->|"성공"| I["Token (인증 완료)
━━━━━━━━━━━━━━━━━━
principal = User('user')
authorities = [ROLE_USER]
authenticated = true"]:::token_after I --> J["SecurityContextHolder 저장"]:::success J --> K["chain.doFilter()"]:::success classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef converter fill:#3d1f5c,stroke:#d2a8ff,color:#ffffff classDef token_before fill:#5c3d00,stroke:#d29922,color:#ffffff classDef token_after fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef manager fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef fail fill:#5c1a1a,stroke:#f85149,color:#ffffff classDef success fill:#1a3a2a,stroke:#3fb950,color:#ffffff
5. 기억하기 인증 - rememberMe()
개념
RememberMe 인증은 사용자가 웹 사이트나 애플리케이션에 로그인할 때 자동으로 인증 정보를 기억하는 기능이다. UsernamePasswordAuthenticationFilter와 함께 사용되며, AbstractAuthenticationProcessingFilter 슈퍼클래스에서 훅을 통해 구현된다.
- 인증 성공 시
RememberMeServices.loginSuccess()를 통해 토큰을 생성하고 쿠키로 전달 - 인증 실패 시
RememberMeServices.loginFail()을 통해 쿠키를 지움 LogoutFilter와 연계해서 로그아웃 시 쿠키를 지움
토큰 생성 구조
user:expiry:1111:security)"]:::algo end HASH --> COOKIE subgraph COOKIE["생성된 쿠키"] direction TB C1["cookieName = 'remember-me'"]:::cookie C2["cookieValue = base64(
user:expiry:SHA-256:hash)"]:::cookie C3["maxAge = 3600"]:::cookie C1 --- C2 --- C3 end classDef f fill:#2d333b,stroke:#768390,color:#e6edf3 classDef algo fill:#3d1f5c,stroke:#d2a8ff,color:#ffffff classDef cookie fill:#5c3d00,stroke:#d29922,color:#ffffff
RememberMeServices 구현체
| 구현체 | 설명 |
|---|---|
TokenBasedRememberMeServices | 쿠키 기반 토큰의 보안을 위해 해싱을 사용한다 |
PersistentTokenBasedRememberMeServices | 생성된 토큰을 데이터베이스나 영구 저장 매체에 저장한다 |
두 구현 모두 UserDetailsService가 필요하다.
rememberMe() API
http.rememberMe(httpSecurityRememberMeConfigurer -> httpSecurityRememberMeConfigurer
.alwaysRemember(true) // 매개변수 미설정 시에도 쿠키 항상 생성
.tokenValiditySeconds(3600) // 토큰 유효 시간 (초)
.userDetailsService(userDetailService) // UserDetails 조회 서비스
.rememberMeParameter("remember") // 기억하기 HTTP 매개변수 (기본값: remember-me)
.rememberMeCookieName("remember") // 쿠키 이름 (기본값: remember-me)
.key("security") // 토큰 식별 키
);
6. 기억하기 인증 필터 - RememberMeAuthenticationFilter
개념
RememberMeAuthenticationFilter는 SecurityContextHolder에 Authentication이 포함되지 않은 경우에 실행되는 필터이다. 세션이 만료되었거나 애플리케이션 종료로 인해 인증 상태가 소멸된 경우, 토큰 기반 인증을 사용해 유효성을 검사하고 자동 로그인 처리를 수행한다.
동작 흐름
GET /user
Cookie: remember-me=..."]:::client --> B["RememberMeAuthenticationFilter"]:::filter B --> C{"SecurityContextHolder
Authentication != null?"} C -->|"Y"| D["chain.doFilter()"]:::skip C -->|"N (세션 만료)"| E["RememberMeServices.autoLogin()
━━━━━━━━━━━━━━━━━━
클라이언트 쿠키 ↔ 서버 쿠키 비교"]:::remember E --> F["RememberMeAuthenticationToken
━━━━━━━━━━━━━━━━━━
principal = User('user')
authorities = [ROLE_USER]"]:::token F --> G["AuthenticationManager"]:::manager G --> H{"인증 성공?"} H -->|"실패"| I["loginFail()
remember-me 쿠키 삭제"]:::fail H -->|"성공"| J["SecurityContextHolder 저장"]:::success J --> K["SecurityContextRepository
세션에 저장"]:::success K --> L["ApplicationEventPublisher"]:::success classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef remember fill:#3d1f5c,stroke:#d2a8ff,color:#ffffff classDef token fill:#5c3d00,stroke:#d29922,color:#ffffff classDef manager fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef fail fill:#5c1a1a,stroke:#f85149,color:#ffffff classDef success fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef skip fill:#2d333b,stroke:#768390,color:#e6edf3
7. 익명 사용자 - anonymous()
개념
Spring Security에서 "익명으로 인증된" 사용자와 인증되지 않은 사용자 간에 실제 개념적 차이는 없다. 단지 액세스 제어 속성을 구성하는 더 편리한 방법을 제공한다.
SecurityContextHolder가 항상Authentication객체를 포함하고null을 포함하지 않는다는 규칙을 세우면 클래스를 더 견고하게 작성할 수 있다- 인증 사용자와 익명 인증 사용자를 구분해서 어떤 기능을 수행하고자 할 때 유용하다
- 익명 인증 객체는 세션에 저장하지 않는다
- 익명 인증 사용자의 권한을 별도로 운용할 수 있다
동작 흐름
GET /index (인증 없음)"]:::client --> B["AnonymousAuthenticationFilter"]:::filter B --> C{"SecurityContextHolder
Authentication != null?"} C -->|"Y"| D["chain.doFilter()
기존 인증 유지"]:::skip C -->|"N"| E["AnonymousAuthenticationToken 생성
━━━━━━━━━━━━━━━━━━
principal = 'anonymousUser'
authorities = [ROLE_ANONYMOUS]
authenticated = true"]:::token E --> F["SecurityContextHolder
(세션에는 저장하지 않음)"]:::holder classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef token fill:#5c3d00,stroke:#d29922,color:#ffffff classDef holder fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef skip fill:#2d333b,stroke:#768390,color:#e6edf3
스프링 MVC에서 익명 인증 사용하기
// Authentication 타입으로 익명 사용자 구분
public String method(Authentication authentication) {
if (authentication instanceof AnonymousAuthenticationToken) {
return "anonymous";
} else {
return "not anonymous";
}
}
// @CurrentSecurityContext로 SecurityContext 직접 접근
public String method(@CurrentSecurityContext SecurityContext context) {
return context.getAuthentication().getName();
}
HttpServletRequest#getPrincipal을 사용하여 파라미터를 해결하는데, 요청이 익명일 때 이 값은 null이다. 익명 요청에서 Authentication을 얻고 싶다면 @CurrentSecurityContext를 사용한다.
8. 로그 아웃 - logout()
개념
Spring Security는 기본적으로 DefaultLogoutPageGeneratingFilter를 통해 로그아웃 페이지를 제공하며 GET /logout URL로 접근이 가능하다. 로그아웃 실행은 기본적으로 POST /logout으로만 가능하나, CSRF 비활성화 시 GET, PUT, DELETE 모두 가능하다.
logout() API
http.logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer
.logoutUrl("/logoutProc") // 로그아웃 URL (기본값: /logout)
.logoutRequestMatcher( // RequestMatcher 지정 (logoutUrl보다 우선)
new AntPathRequestMatcher("/logoutProc", "POST"))
.logoutSuccessUrl("/logoutSuccess") // 로그아웃 후 리다이렉션 URL
.logoutSuccessHandler((req, res, auth) -> {
res.sendRedirect("/logoutSuccess"); // LogoutSuccessHandler (logoutSuccessUrl 무시됨)
})
.deleteCookies("JSESSIONID", "CUSTOM_COOKIE") // 삭제할 쿠키
.invalidateHttpSession(true) // 세션 무효화 (기본값: true)
.clearAuthentication(true) // Authentication 삭제
.addLogoutHandler((req, res, auth) -> {}) // 추가 LogoutHandler
.permitAll()
);
LogoutFilter 동작 흐름
POST /logout"]:::client --> B["LogoutFilter"]:::filter B --> C{"RequestMatcher
매칭 확인"} C -->|N| D["chain.doFilter()"]:::skip C -->|Y| E["CompositeLogoutHandler"]:::handler subgraph HANDLERS["LogoutHandler 순차 실행"] direction TB H0["① CookieClearingLogoutHandler
쿠키 삭제"]:::h H1["② 사용자 정의 Handler"]:::h H2["③ CsrfLogoutHandler
CSRF 토큰 삭제"]:::h H3["④ SecurityContextLogoutHandler
세션 무효화 + Context 삭제"]:::h H4["⑤ LogoutSuccessEventPublishingLogoutHandler"]:::h H0 --> H1 --> H2 --> H3 --> H4 end E --> HANDLERS HANDLERS --> F["LogoutSuccessHandler
━━━━━━━━━━━━━━━━━━
targetUrl = '/login?logout'"]:::success F --> G["Client
302 Redirect"]:::client classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef handler fill:#3d1f5c,stroke:#d2a8ff,color:#ffffff classDef h fill:#2d333b,stroke:#768390,color:#e6edf3 classDef success fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef skip fill:#2d333b,stroke:#768390,color:#e6edf3
9. 요청 캐시 - RequestCache / SavedRequest
개념
RequestCache는 인증 절차 문제로 리다이렉트된 후에 이전 요청 정보를 담고 있는 SavedRequest 객체를 세션에 저장하고 필요 시 다시 가져와 실행하는 캐시 메커니즘이다. 기본 구현체로 HttpSessionRequestCache를 사용한다.
SavedRequest는 인증 절차 후 사용자를 이전 페이지로 안내하며 이전 요청 관련 정보를 저장한다. 기본 구현체로 DefaultSavedRequest를 사용한다.
동작 흐름
redirectUrl = "/user"
method = "GET" Session-->>Client: 302 → /login Note over Client: 로그인 시도 Client->>Login: POST /login Login->>ASH: 인증 성공 ASH->>Cache: getRequest() Cache->>Session: SPRING_SECURITY_SAVED_REQUEST 조회 Session-->>Cache: redirectUrl = "/user" Cache-->>ASH: getRedirectUrl() ASH-->>Client: 302 → /user Note over Client: 원래 요청 페이지로 이동
requestCache() API
// 커스텀 매칭 파라미터 설정
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("customParam=y");
http.requestCache(cache -> cache.requestCache(requestCache));
// 요청 저장 비활성화
RequestCache nullRequestCache = new NullRequestCache();
http.requestCache(cache -> cache.requestCache(nullRequestCache));
RequestCacheAwareFilter
RequestCacheAwareFilter는 이전에 저장했던 웹 요청을 다시 불러온다. SavedRequest가 현재 Request와 일치하면 저장된 요청을 필터 체인에 전달하고, 없으면 원래 Request를 그대로 진행시킨다.
GET /user"]:::client --> B["RequestCacheAwareFilter"]:::filter B --> C{"SavedRequest
!= null?"} C -->|"N"| D["chain.doFilter(request, response)
원래 요청"]:::skip C -->|"Y"| E{"SavedRequest ==
currentRequest?"} E -->|"N"| F["chain.doFilter(request, response)"]:::skip E -->|"Y"| G["chain.doFilter(SavedRequest, response)
저장된 요청으로 진행"]:::success classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef skip fill:#2d333b,stroke:#768390,color:#e6edf3 classDef success fill:#1a3a2a,stroke:#3fb950,color:#ffffff
정리
- 폼 인증(formLogin):
UsernamePasswordAuthenticationFilter가 처리하며, 로그인 페이지/URL/성공-실패 핸들러를 커스터마이징할 수 있다 - HTTP Basic 인증(httpBasic):
BasicAuthenticationFilter가 Authorization 헤더의 Base64 자격 증명을 처리하며, 반드시 HTTPS와 함께 사용해야 한다 - 기억하기 인증(rememberMe):
RememberMeAuthenticationFilter가 세션 만료 후에도 쿠키 기반 토큰으로 자동 로그인을 수행한다 - 익명 사용자(anonymous):
AnonymousAuthenticationFilter가 인증되지 않은 요청에AnonymousAuthenticationToken을 생성하여 SecurityContextHolder가 항상 Authentication을 포함하도록 보장한다 - 로그아웃(logout):
LogoutFilter가 세션 무효화, 쿠키 삭제, SecurityContext 정리를CompositeLogoutHandler를 통해 처리한다 - 요청 캐시(RequestCache):
RequestCacheAwareFilter가 인증 전 요청 정보를 세션에 저장하고, 인증 성공 후 원래 요청 URL로 리다이렉트한다
'Java & Spring > Spring Security' 카테고리의 다른 글
| 06. 예외 처리 (0) | 2026.08.05 |
|---|---|
| 05. 세션 관리 (0) | 2026.08.05 |
| 04. 인증 상태 영속성 (0) | 2026.08.04 |
| 03. 인증 아키텍처 (0) | 2026.08.04 |
| 01. 초기화 과정 이해 (0) | 2026.08.01 |