[Spring Security] 인증 메커니즘
기본적으로 HTTP 기반 form 로그인 인증을 사용한다.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin((form) -> form
.loginPage("/login")
.loginProcessingUrl("/loginProcess")
.defaultSuccessUrl("/", true)
.failureUrl("/failed")
.usernameParameter("username")
.passwordParameter("password")
.failureHandler(null)
.successHandler(null)
.permitAll()
);
return http.build();
}
loginPage : 사용자 정의 로그인 페이지를 설정한다. 컨트롤러에 지정된 URL을 사용하자. (JSP 사용 시 WEB-INF는 permitAll로 풀어줘야 한다)
loginProcessingUrl : 기본 로그인 창의 action으로 생각하면 된다. 이름과 비밀번호를 검증할 URL을 지정한다.
defaultSuccessUrl : 로그인 성공 시 이동할 페이지를 지정한다. true 설정 시 무조건 지정된 위치로 이동시키니, 인증 없이 사용하다가 인증이 필요한 페이지를 만나고 인증을 진행했을 때 해당 위치로 리다이렉트 해야 하는 경우 false를 사용하자.
usernameParameter : 인증 수행 시 아이디를 찾을 때 확인하는 HTTP 매개변수로, input 태그의 name을 생각하면 된다. 기본값은 username이다.
passwordParameter : 마찬가지..
Handler : 인증이 실패하거나 성공했을 때 사용할 핸들러를 지정한다.
마무리로 permitAll()을 설정할 경우 failureUrl(), loginPage(), loginProcessingUrl() 에 대한 접근을 허용한다.
// Customizer.java
@FunctionalInterface
public interface Customizer<T> {
/**
* Performs the customizations on the input argument.
* @param t the input argument
*/
void customize(T t);
/**
* Returns a {@link Customizer} that does not alter the input argument.
* @return a {@link Customizer} that does not alter the input argument.
*/
static <T> Customizer<T> withDefaults() {
return (t) -> {
};
}
}
스프링 시큐리티 6부터는 시큐리티 설정 시 Customzier 함수형 인터페이스를 사용한다.
람다 표현식으로 함수형 인터페이스의 구현체를 쉽게 생성할 수 있으니.. 시큐리티 설정 시에는 람다 표현식을 사용하자.
스프링 시큐리티는 AbstractAuthenticationProcessingFilter 클래스를 자격 증명의 기본 필터로 사용한다.
사용자가 직접 인증 로직을 작성하려면 해당 클래스를 상속받아 attemptAuthentication 메서드를 오버라이드 해야 한다.
스프링 시큐리티가 기본적으로 제공하는 UsernamePasswordAuthenticationFilter 의 프로세스를 살펴보자.
사용자가 /login URL으로 요청 시 해당 필터가 요청을 가로채 RequestMatcher로 자신이 해당 요청을 처리할 지 판단한다.
이후 Token에 Username과 Password를 저장하고, AuthenticationManager가 실제 인증 로직을 수행한다.
인증에 성공했으면 User와 User가 가진 Authority를 가지는 객체를 생성하고, 세션 관련 작업을 수행한다.
사용자 객체를 SecurityContext에 저장하고 HTTP 세션에 SecurityContext를 저장해 사용자의 인증 상태를 기억한다.
// UsernamePasswsordAuthenticationToken.java
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
// HttpSessionSecurityContextRepository.java
private void setContextInSession(SecurityContext context, HttpSession session) {
if (session != null) {
session.setAttribute(this.springSecurityContextKey, context);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Stored %s to HttpSession [%s]", context, session));
}
}
}
애플리케이션을 디버깅 모드로 실행시키고 하나한 따라가보면.. 해당 프로세스대로 진행됨을 확인할 수 있다.
인증 실패 시 예외가 발생해 필터가 관련 예외를 처리한다. (SecurityContextHolder 삭제, RememberMeServices 호출...)
스프링 시큐리티는 HttpBasic 인증 방식도 제공한다.
서버는 클라이언트에게 인증요구를 보낼 때 WWW-Authenticate 헤더를 기술해 realm과 인증방식을 보내고,
클라이언트는 서버로 접속 시 Base64 방식으로 username과 password를 인코딩하고 Authorization 헤더에 포함시킨다.
HttpBasic 방식은 디코딩으로 타인이 쉽게 인증 정보를 확인할 수 있으니 ssl 인증서를 발급해 안전한 Https 프로토콜과 사용해야 한다.
보통 formLogin 으로 Session-Cookie 기반 인증을 사용하거나, JWT Filter를 구성하고 추가하는 방식으로 인증을 수행하지만, 간단한 애플리케이션을 구현할 때는 HttpBasic 을 사용하기도 한다.
인증 방식이 다를 뿐 스프링 시큐리티는 SecurityContextHolder, AuthenticationManager 등 여러 구성 요소를 사용해 일관성 있게 인증 방식을 처리한다.
formLogin 방식을 사용할 때는 UsernamePasswordAuthenticationFilter를 사용하는데, 이 때 RememberMe 인증을 추가로 사용할 수 있다.
인증 성공 시 loginSuccess() 메서드로 RememberMe 토큰을 생성하고 쿠키로 전달하고, 실패 시 loginFail() 메서드로 쿠키를 지운다.
토큰은 기본적으로 암호화된 상태이고 브라우저에 쿠키로 저장된다.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
.rememberMe((rememberMe) -> rememberMe
.alwaysRemember(true)
.tokenValiditySeconds(3600)
.userDetailsService(userDetailsService)
.rememberMeParameter(null)
.rememberMeCookieName(null)
.key("security")
);
return http.build();
}
alwaysRemember : 매개변수가 설정되지 않았을 때 (체크박스 선택하지 않았을 때) 도 쿠키를 생성하는 옵션이다.
tokenValiditySeconds : 토큰이 유효한 기간을 지정한다.
userDetailsService : UserDetails 를 조회할 때 사용되는 서비스 Layer를 지정한다.
rememberMeParameter : 사용자를 기억할 떄 사용하는 Http 매개변수로, 체크박스의 name으로 생각하면 된다.
rememberMe 설정으로 브라우저를 닫거나 컴퓨터를 꺼도 발행된 토큰으로 다시 로그인하지 않아도 인증 상태를 유지한다.
formLogin 을 사용하는 경우 login과 logout 기능을 모두 제공하지만, login 페이지를 직접 만들어서 사용하는 경우 logout 기능도 개발자가 직접 구현해야 한다.
.logout(logout -> logout
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"))
.logoutSuccessUrl("/login")
.logoutSuccessHandler(null)
.deleteCookies("JSESSIONID", "COOKIE_NAME..")
.invalidateHttpSession(true)
.clearAuthentication(true)
.addLogoutHandler(null)
.permitAll()
logoutUrl, logoutRequestMatcher : 로그아웃 URL과 HTTP 메서드를 지정한다. logoutRequestMatcher가 우선된다.
deleteCookies : 로그아웃 시 삭제할 쿠키 이름을 지정한다.
invalidateHttpSession : 기본 값은 true이고, true로 설정 시 로그아웃 시 현재 HTTP 세션을 무효화한다.
clearAuthentication : 로그아웃 시 스프링 시큐리티의 SecurityContext에서 인증 정보를 제거할지를 결정한다.
사용자가 인증받기 전에 인증이 필요한 페이지로 접근 시 HttpSessionRequestCache 캐시 객체가 HttpSession에 인증 이전의 요청 정보를 저장한다.
이후 인증 성공 시 HttpSession에서 사용자가 가려고 했던 URL을 꺼내 해당 경로로 리다이렉트한다.
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("customParam=y");
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.requestCache(cache -> cache.requestCache(requestCache))
스프링 시큐리티가 기본적으로 URL을 세션에 저장했다가 리다이렉트 하는 기능을 제공하긴 하지만..
일반적인 로그인을 수행하는 경우 리다이렉트 기능이 필요하지 않을 수 있다.
이 때 setMatchingRequestParameterName으로 파라미터를 설정해 특정 요청 파라미터가 일치하는 요청만 캐시하도록 설정할 수 있다.
예제는 해당 파라미터가 포함된 요청만 캐시하도록 설정해 불필요한 작업을 줄인다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] 인증 상태 영속성 (0) | 2024.06.10 |
---|---|
[Spring Security] 인증 아키텍처 (0) | 2024.06.08 |
[Spring Security] 초기화 과정 (0) | 2024.05.27 |
[Spring Security6] 인증 서버 구축 (0) | 2023.08.15 |
[Spring Security6] OAuth2와 OpenID Connect (0) | 2023.08.13 |
댓글
이 글 공유하기
다른 글
-
[Spring Security] 인증 상태 영속성
[Spring Security] 인증 상태 영속성
2024.06.10 -
[Spring Security] 인증 아키텍처
[Spring Security] 인증 아키텍처
2024.06.08 -
[Spring Security] 초기화 과정
[Spring Security] 초기화 과정
2024.05.27 -
[Spring Security6] 인증 서버 구축
[Spring Security6] 인증 서버 구축
2023.08.15