UsernamePasswordAuthenticationFilter

  1. Spring Security UsernamePasswordAuthenticationFilter
    1. UsernamePasswordAuthenticationFilter
    2. AuthenticationManager
    3. AuthenticationProvider
    4. SecurityContext 에 인증 객체 저장

Spring Security UsernamePasswordAuthenticationFilter

Login Form 인증을 담당하고 있는 UsernamePasswordAuthenticationFilter 를 통해 처리 Flow 를 알아보자

1.PNG

UsernamePasswordAuthenticationFilter 는 사용자가 Form Login 방식에서 로그인 하게 되면

담당 및 처리 하는 Filter 입니다.

사용자가 로그인시 Spring Security 에서 거쳐가는 흐름을 하나하나씩 알아보자

UsernamePasswordAuthenticationFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}

Authentication authResult;
try {
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}

this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}

if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}

this.successfulAuthentication(request, response, chain, authResult);
}
}


}

사용자가 아이디 및 비밀번호를 입력 후 전송 버튼을 클릭 하게 되면(FORM 로그인 방식)

UsernamePasswordAuthenticationFilter 부모 클래스인 AbstractAuthenticationProcessingFilter 가 먼저 담당 하게 된다.

즉 인증 처리를 담당 하고 처리 할 수 있도록 하는 Filter 입니다.

1
2
3
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
}
1
2
3
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return this.requiresAuthenticationRequestMatcher.matches(request);
}

기존 지정된 login 페이지가 아니라면 chain.doFilter 즉 다음 필터로 가서 인증 Flow 로 가지 않고

지정한 것에 login 페이지가 일치 하게 된다면 인증 Flow 로 가게 된다.

1
2
// AbstractAuthenticationProcessingFilter
authResult = this.attemptAuthentication(request, response);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// UsernamePasswordAuthenticationFilter
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());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}

if (password == null) {
password = "";
}

username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}

그다음 attemptAuthentication 메소드를 호출 하는데 구현체인 UsernamePasswordAuthenticationFilter 의
attemptAuthentication 메소드를 호출 하게 된다.

이 메소드는 입력한 아이디 및 비밀번호를 추출 하고 이것을 이용해 Authentication 객체를 생성 한다.

1
return this.getAuthenticationManager().authenticate(authRequest);

Authentication 객체를 만들어서 AuthenticationManager 로 전달 하게 된다.

AuthenticationManager

Authentication 받고 총괄적으로 인증 처리 하는 것이 AuthenticationManager 객체 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// AuthenticationManager 구현체인 ProviderManager 클래스
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
... 생략 ...

Iterator var8 = this.getProviders().iterator();

while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();

... 생략 ...

try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
}

... 생략 ...
}

여러 AuthenticationProvider 중에 FORM 로그인 관련 된 Provider 를 loop 통해 해당 되는 것만 가져오는 로직 이다.

1
result = provider.authenticate(authentication);

그리고 AuthenticationProvider 에게 인증 처리를 위임 하게 된다.

AuthenticationProvider

실질적으로 인증 처리를 담당 하는 것이 AuthenticationProvider 라고 할 수 있다.

기본 Default 인 AuthenticationProvider 구현체 AbstractUserDetailsAuthenticationProvider 에서 처리하지만

대부분 협업에서는 AuthenticationProvider Override 해서 구현 한다. 이 글에서는 실제로 구현체를 직접 만들어서 설명 하고자 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 직접 AuthenticationProvider 구현체인 CustomAuthenticationProvider 클래스를 만들었다. 
public class CustomAuthenticationProvider implements AuthenticationProvider {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private PasswordEncoder passwordEncoder;

@Override
@Transactional
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();

AccountContext accountContext = (AccountContext) userDetailsService.loadUserByUsername(username);

if (!passwordEncoder.matches(password, accountContext.getAccount().getPassword())) {
throw new BadCredentialsException("BadCredentialsException");
}

return new AbstractAuthenticationToken(accountContext.getMember(), null, accountContext.getAuthorities());
}

해당 Authentication 인증 객체 통해 실제 저장된 해당 사용자 데이터를 DB 통해 가져와

매칭 하고 성공 및 실패에 따라 각각 다른 역활을 하게 된다. (UserDetailsService 에 대한 자세한 설명은 나중에 하도록 하겠다.)

실패 경우 AuthenticationException trow 하게 된다.

성공 경우 AbstractAuthenticationToken 객체를 생성 후 AuthenticationProvider 그리고 다시 AuthenticationManager 에게 다시 되돌려 준다.

SecurityContext 에 인증 객체 저장

1
2
3
4
5
6
7
8
9
10
11
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
... 생략 ...
Authentication authResult;
try {
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}

... 생략 ...
this.successfulAuthentication(request, response, chain, authResult);

인증 성공 후 Authentication 객체 생성 하고 AbstractAuthenticationProcessingFilter 까지 다시 전달 하게 된다.

전달 받은 객체 authResult 는

1
2
// AbstractAuthenticationProcessingFilter 클래스
this.successfulAuthentication(request, response, chain, authResult);
1
2
3
4
5
6
7
8
9
// AbstractAuthenticationProcessingFilter 클래스
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
.. 생략 ...

SecurityContextHolder.getContext().setAuthentication(authResult);
... 생략...

this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

SecurityContext 에 해당 인증된 객체를 저장 하고
successHandler 통해 미리 지정한 success Handler 로 전달 하게 된다.


Copyright 2020- syh8088. 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

💰

×

Help us with donation