Spring Security는 Filter를 통해 요청을 처리한다.
- 로그인 요청
- UsernamePasswordAuthenticationFilter가 요청을 처리한다.
- Authentication 객체를 생성한다. (UsernamePasswordAuthenticationToken)
- AuthenticationManager를 통해 인증을 처리한다. (ProviderManager)
- AuthenticationProvider를 통해 인증을 처리한다.
- 인증 처리 과정 (UserDetailsService를 통해 사용자 정보를 조회한다.)
- 인증이 성공하면 인증된 Authentication 객체를 생성한다. (UsernamePasswordAuthenticationToken)
- 인증이 성공하면 SecurityContextHolder에 인증된 Authentication 객체를 저장한다.
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// ...
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// ...
}
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean {
// ...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresAuthentication(request, response)) {
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
else {
// The request match the configured pattern, but failed
// authentication for some reason. Continue chain with
// SecurityContextHolder populated with SecurityContext
// from the session (or a new one if none was present)
chain.doFilter(request, response);
}
}
// ...
}
위 코드를 살펴보자 Spring Security Config 설정에서 login url 을 /login 으로 설정했기 때문에 /login 요청을 처리한다.
requiresAuthentication()
이 메소드는 client 에서 요청한 url이 /login 이 맞는지 확인한다.
로그인이 필요할 때 /login url로 요청을 보내는데, 이 요청을 처리하기 위해서는 인증이 필요하다.
다른 url로 들어오는 요청은 인증이 필요하지 않기 때문에 /login url로 요청이 들어오면 인증이 필요하다고 판단한다.
다시 말하지만 세션 로그인 과정이다. 타 url로 들어오는 요청 또한 인증된 authentication 객체 정보가 필요 하지만
이미 인증이 되어있기 때문에 인증이 필요하지 않다고 판단한다.
attemptAuthentication()
successfulAuthentication()
unsuccessfulAuthentication()
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
// ...
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
// (2)
public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
}
// ...
}
public interface Authentication extends Principal, Serializable {
// ...
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
String getName();
// ...
}
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
// ...
private List<AuthenticationProvider> providers = Collections.emptyList();
// ...
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// ...
AuthenticationException lastException = null;
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
Authentication result = provider.authenticate(toTest);
if (result != null) {
copyDetails(authentication, result);
return result;
}
}
// ...
}
// ...
}
구현체가 하는 역할은 다음과 같다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
// ...
private UserDetailsService userDetailsService;
// ...
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
// ...
}
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
보통은 위 인터페이스를 구현한 클래스를 만들어서 사용한다.
public class User implements UserDetails {
// ...
private final String username;
private final String password;
private final Collection<? extends GrantedAuthority> authorities;
// ...
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
// ...
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
자 그러면 Authentication 객체가 principal과 credentials를 가지고 있는 더 상위의 개념이라고 간단하게 이해할 수 있다.