UserNamePasswordAuthenticationFilter 목적
이 필터는 DefaultSecurityFilterChain에 기본적으로 등록되는 필터로 여덟 번째에 위치한다.
이 필터가 등록되는 목적은 POST : "/login" 경로에서 Form 기반 인증을 진행할 수 있도록
multipart/form-data 형태의 username/password 데이터를 받아 인증 클래스에게 값을 넘겨주는 역할을 수행한다.
커스텀 SecurityFilterChain을 생성하면 자동 등록이 안되기 때문에 아래 구문을 통해서 필터를 활성화시켜야 한다.
http
.formLogin(Customizer.withDefaults());
UsernamePasswordAuthenticationFilter 클래스
public class UsernamePasswordAuthenticationFIlter extends AbstractAuthenticationProcessingFilter {
}
doFilter가 없다 . doFilter는?
앞서 봐왔던 필터들과 다르게 doFilter 메소드가 보이지 않는다. 어디 있을까? 바로 부모 클래스인 AbstractAuthenticationProcessingFilter 클래스에 존재한다.
왜?
UsernamePasswordAuthenticationFilter는 Form 로그인 방식에 대한 필터이다. Form 데이터를 받은 후 인증 과정은 어떻게 될까?
과정
-> 사용자에게 데이터를 받아 인증 -> 인증 결과 -> 성공/실패 핸들
사용자가 보낸 데이터 방식이 다르다고해서 위 과정이 변할까?
username/password를 Form,JSON 방식으로 보낸다고해서 위 과정이 변할까? 아니다.
따라서 위 과정에 대한 추상 클래스인 AbstractAuthenticationProcessingFilter를 정의하고 각각의 방식에 따라 필터를 구현해서 사용한다.
AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
}
doFilter : 주요 로직
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//로그인 경로 요청인지 확인
if(!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
// 로그인 과정 시도
try {
//사용자로부터 데이터를 받아 상황에 맞는 인증을 진행 (이 부분을 구현)
Authentication authenticationResult = attemptAuthentication(request, response)
if (authenticationResult == null) {
return;
}
//인증 결과가 존재하면 세션 전략에 따라 SecurityContext에 저장
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// 아래 값이 설정되어 있으면 다음 필터로 넘김
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFIlter(request,response)
}
위의 다이어그램은 Spring Security의 인증 과정을 설명하는 것으로, 사용자 정보(username과 password)를 받아 인증이 어떻게 처리되는지 보여줍니다. 이를 통해 Spring Security가 요청을 받았을 때 AuthenticationManager, AuthenticationProvider, UserDetailsService가 협력하여 인증을 수행하는 과정을 설명하겠습니다.
인증 과정 상세 설명
- Security Filter Chain:
- 클라이언트로부터 요청이 들어오면, 이 요청은 Spring Security의 Filter Chain을 거치게 됩니다.
- Filter Chain은 요청에 대해 일련의 보안 필터를 적용하는데, 이 중 로그인 요청을 처리하는 필터가 UsernamePasswordAuthenticationFilter와 같은 로그인 필터입니다.
- Login Filter (예: UsernamePasswordAuthenticationFilter):
- UsernamePasswordAuthenticationFilter는 **사용자가 보낸 로그인 요청(아이디와 비밀번호)**을 처리하는 필터입니다.
- 로그인 요청이 들어오면, attemptAuthentication 메서드를 통해 사용자 정보(username과 password)를 추출하고, 이를 AuthenticationManager에게 전달하여 인증을 요청합니다.
- AuthenticationManager:
- AuthenticationManager는 인증을 담당하는 주요 인터페이스로, 여러 인증 방식을 지원할 수 있도록 설계되었습니다.
- ProviderManager라는 기본 구현체를 사용하며, 이는 여러 AuthenticationProvider를 가지고 있어, 여러 가지 인증 방식을 순차적으로 시도합니다.
- AuthenticationManager는 받은 인증 요청을 적합한 AuthenticationProvider에 전달합니다.
- AuthenticationProvider:
- AuthenticationProvider는 실제로 인증을 수행하는 인터페이스로, 다양한 방식의 인증을 지원합니다.
- Spring Security에서는 일반적으로 **DaoAuthenticationProvider**가 사용됩니다. 이는 username/password 기반의 인증을 처리하는 구현체로, 사용자 정보를 데이터베이스에서 불러와 인증합니다.
- AuthenticationProvider는 로그인 요청의 인증 정보(username, password)와 저장된 데이터베이스의 사용자 정보를 비교하여 인증을 수행합니다.
- DaoAuthenticationProvider와 UserDetailsService:
- DaoAuthenticationProvider는 UserDetailsService와 협력하여 사용자 정보를 로드합니다.
- UserDetailsService는 데이터베이스나 다른 저장소에서 사용자 정보를 조회하여, UserDetails 객체로 반환합니다.
- UserDetails 객체는 사용자 아이디, 비밀번호, 권한 정보 등을 포함하며, 이 정보가 로그인 요청의 정보와 일치하는지 확인합니다.
- 만약 사용자가 입력한 비밀번호와 저장된 비밀번호가 일치하면 인증이 성공합니다.
- 인증 성공 또는 실패 처리:
- DaoAuthenticationProvider는 인증이 성공하면 Authentication 객체를 반환하고, 이를 AuthenticationManager가 반환합니다.
- UsernamePasswordAuthenticationFilter는 인증 성공 시 해당 세션에 SecurityContext를 저장하여 인증된 상태를 유지합니다.
- 인증에 실패할 경우, AuthenticationException을 발생시켜 실패 처리를 수행합니다.
- UserDetailsService와 UserDetails:
- UserDetailsService는 사용자 정보를 조회하는 서비스로, 데이터베이스나 외부 API에서 사용자 정보를 불러옵니다.
- UserDetails는 조회된 사용자 정보를 포함하는 객체로, 사용자 이름, 암호화된 비밀번호, 권한 등을 담고 있습니다.
- 이 UserDetails 객체는 DaoAuthenticationProvider가 사용자의 비밀번호와 일치하는지 확인하는 데 사용됩니다.
전체 인증 흐름 요약
- 사용자가 로그인 요청을 보냅니다 (username과 password).
- **UsernamePasswordAuthenticationFilter**가 요청을 받아 AuthenticationManager에게 인증을 위임합니다.
- **AuthenticationManager**는 요청을 AuthenticationProvider로 전달합니다.
- **DaoAuthenticationProvider**가 UserDetailsService를 통해 사용자 정보를 조회하고, 입력된 정보와 저장된 정보를 비교합니다.
- 비밀번호가 일치하면 인증 성공, 그렇지 않으면 인증 실패로 처리됩니다.
- 인증이 성공하면 SecurityContext에 인증 정보를 저장하여 이후의 요청에서도 인증된 상태를 유지할 수 있게 됩니다.
AuthenticationManager의 역할
AuthenticationManager는 인증을 총괄하는 인터페이스로, 인증 요청을 받아서 적절한 AuthenticationProvider에게 전달하는 역할을 합니다.
- 주요 역할 : AuthenticationManager는 인증이 필요한 요청을 처리하는 엔트리 포인트입니다. 인증을 시도할 때, 여러 개의 AuthenticationProvider를 가지고 있는 경우 순차적으로 Provider에게 인증 요청을 위임합니다.
- 구현체 : AuthenticationManager의 기본 구현체는 ProviderManager이다.
- ProviderManager는 여러 개의 AuthenticationProvider를 관리하며, 순차적으로 각 Provider에게 인증을 요청합니다.
- 만약 하나의 AuthenticationProvider가 인증을 성공하면, AuthenticationManager는 인증된 객체(Authentication)를 반환하고, 인증이 실패하면 다음 Provider로 요청을 넘기거나 전체적으로 인증 실패를 반환합니다.
AuthenticationProvider의 역할
AuthenticationProvider는 실제로 인증을 수행하는 인터페이스이다. 각 Provider는 특정한 인증 방식에 대한 구현을 담당할 수 있다.
- 주요 역할: AuthenticationProvider는 실제 인증 로직을 포함하고 있다.
- 예를 들어, DaoAuthenticationProvider는 데이터베이스에서 사용자 정보를 불러와 비밀번호를 검증하는 방식으로 인증을 처리한다.
- LdapAuthenticationProvider는 LDAP 서버를 통해 인증을 처리하고 , JwtAuthenticationProvider는 JWT 토큰을 검증하는 방식으로 인증을 처리합니다.
- 다양한 인증 방식 지원 : Spring Security에서는 여러 종류의 AuthenticationProvider를 사용할 수 있으며 , 각 Provider는 특정한 인증 방식에 맞게 구현됩니다.
- 예를 들어, 시스템이 동시에 username/password, OAuth, LDAP 기반의 인증을 지원하려면, 각각의 방식에 대해 DaoAuthenticationProvider, OAuth2AuthenticationProvider, LdapAuthenticationProvider를 정의할 수 있습니다.
AuthenticationProvider에 구현체를 넣어도 되는가?
네, AuthenticationProvider는 직접 구현할 수 있으며 , 다양한 인증 방식에 대한 커스텀 구현체를 넣어도 된다. Spring Security는 확장 가능하도록 설계되어 있어, AuthenticationProvider 인터페이스를 구현하여 필요한 인증 로직을 추가할 수 있다.
예시: 커스텀 AuthenticationProvider 구현
예를 들어, 특정 비즈니스 로직에 맞는 커스텀 인증 방식이 필요할 경우 AuthenticationProvider를 직접 구현할 수 있습니다.
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 커스텀 인증 로직 작성
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 예시: 사용자명과 비밀번호가 일치하는지 검증
if ("user".equals(username) && "password".equals(password)) {
return new CustomAuthenticationToken(username, password, List.of(new SimpleGrantedAuthority("ROLE_USER")));
} else {
throw new BadCredentialsException("Invalid credentials");
}
}
@Override
public boolean supports(Class<?> authentication) {
return CustomAuthenticationToken.class.isAssignableFrom(authentication);
}
}
위 예시는 **커스텀 AuthenticationProvider**를 직접 구현한 것으로, username과 password를 검증하는 커스텀 로직을 작성한 것입니다. supports 메서드는 이 AuthenticationProvider가 처리할 수 있는 Authentication 타입을 지정합니다.
출처 : https://www.youtube.com/watch?v=VGAY9ixWn2w