Spring Security는 어떻게 인증을 처리할까? (2) 인증 처리 과정(form)

Choizz·2023년 1월 23일
0

spring security

목록 보기
1/2
post-thumbnail

오늘 포스팅은 Spring Security 아키텍쳐에 이어서 Spring Security 인증 과정에 대하여 포스팅 하려고 한다.

form 로그인 방식에서의 인증 과정을 다루었다.

참고: https://docs.spring.io/spring-security/reference/servlet/authentication/index.html


1. Spring Security 인증 처리 과정

  • form 로그인 형식에서 인증 처리 과정은 UsernamePasswordAuthenticationfilter에서 이루어지는 과정으로 보면 된다.


2. UsernamePasswordAuthenticationfilter, AbstractAuthenticationProcessingFilter

1) UsernamePasswordAuthenticationfilter

  • form 형식으로 로그인 request가 들어오면(POST, ”/login”)
    UsernamePasswordAuthenticationfilter 로 진입한다. 이 필터는 username(로그인 할 때 id)과 password를 통해 UsernamePasswordAuthenticationToken을 생성한다.

    1): Spring Security는 디폴트 값으로 “username”, “password”를 키값으로 사용한다. 그래서 form형식에서 username으로 키를 설정해야 한다.

    2): 기본 URL 값으로 /login , 메서드는 POST 요청으로 지정되어 있다. HttpSecurity의 loginProcessingUrl() 메서드로 URL 설정이 가능하다.

    3): 인증을 시도하는 메서드이다. username과 password를 사용해서 UsernamePasswordAuthenticationToken 을 생성한다. 그리고 이것을 AuthenticationManager에 전달 하여 인증 처리를 위임한다.

    4): authenticate() 메서드는 Authentication 타입을 파라미터로 받고 Authentication 을 리턴한다. 즉, UsernamePasswordAuthenticationTokenAuthentication 타입이라는 것을 알 수 있다.

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { 
	
	//1)
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; 

	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; 
	
    //2)
	private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login","POST"); 

  ...

	public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);

	//3)
	@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);
    ...

		String password = obtainPassword(request);
    ...
		
   
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
		...
	    //4)
		return this.getAuthenticationManager().authenticate(authRequest); 
	}

  ...

}

2) AbstractAuthenticationProcessingFilter

  • UsernamePasswordAuthenticationFilter의 부모 클래스이다.

    1): 인증에 성공했을 경우의 메서드를 구현하고 있다. UsernamePasswordAuthenticationToken을
    SecurityContext에 저장하고 HttpSession에 SecurityContext를 저장한다.

    2): 인증에 실패할 경우의 메서드를 구현하고 있다. SeucurityContext를 초기화하고, AuthenticationFailureHandler를 호출한다.

        public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
    		implements ApplicationEventPublisherAware, MessageSourceAware {
    
    	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); 
    		...
    		//생략
    	}
    
     ...
    
        //1)
    	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
    			Authentication authResult) throws IOException, ServletException {
    		SecurityContext context = SecurityContextHolder.createEmptyContext();
    		context.setAuthentication(authResult);
    		SecurityContextHolder.setContext(context);
    		this.securityContextRepository.saveContext(context, request, response);
    		//생략
    	}
    
        //2)
    	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
    			AuthenticationException failed) throws IOException, ServletException {
    		SecurityContextHolder.clearContext();
    		//생략
    	}
    
     ...
     ...
    }
        

3.구성 요소

1) SecurityContextHolder, SecurityContext

  • SecurityContext는 SecurityContextHolder에 포함되어 있으며, Authentication을 가지고 있다.
  • SecurityContextHolder는Spring Security가 인증된 사용자의 정보를 저장하는 곳이다. Spring Security는 어떻게 SecurityContextHolder가 채워지는 지는 신경쓰지 않고, 만약 SecurityContextHolder안에 값이 존재한다면 현재 인증된 유저로 사용할 수 있다. 기본적으로 SecurityContextHolder는 유저의 정보(details)를 저장하기 위해 ThreadLocal을 사용한다. 그래서 정보를 담고있는 SecurityContext는 thread-safe하다고 볼 수 있다.
    //코드가 많이 생략되어 있음
    public class SecurityContextHolder {
    
    	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    	
    	...
    
    		if (!StringUtils.hasText(strategyName)) {
    			// Set default
    			strategyName = MODE_THREADLOCAL;
    		}
    		if (strategyName.equals(MODE_THREADLOCAL)) {
    ////////////////////////////////////////// 스레드 로컬 /////////////
    			strategy = new ThreadLocalSecurityContextHolderStrategy(); 
    			return;
    		}
    	...
    }
    ...
    //생략
    }

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/securitycontextholder.png

2) Authentication

  • Authentication 인터페이스는 2가지 주요 용도로 사용된다.

    • 1): AuthenticationManager에게 유저 인증을 위한 입력값으로 사용된다. 즉, AuthenticationManager에게 Authentication 타입의 UsernamePasswordAuthenticationToken을 넘겨주어 인증을 처리하게 한다.
    • 2) : 인증이 완료된 후에 현재 인증된 유저라는 것을 나타낸다. 즉, SecurityContext에 인증된 Authentication이 저장된다.
  • Authentication은

    • principal : username/password과 관련된 인증에서 주로 UserDetails 인스턴스.
    • credentials : 주로 password이고, 보안을 위해서 인증이 완료된 후에 지워짐.
    • authorities: user에게 부여된 권한.

    을 가지고 있다.

3) GranedAuthority

  • 유저에게 주어진 권한이다.
  • form 로그인 방식을 사용할 때, UserDetailService를 통해 로드된다.

4) UserDetailsService

  • UserDetails를 로드하는 인터페이스이다.
public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

5) UserDetails

  • username과 password, authorites를 저장하는 컴포넌트이다.
  • AuthenicationProvider는 이것을 이용해 자격 증명을 수행한다.
public interface UserDetails extends Serializable {

	Collection<? extends GrantedAuthority> getAuthorities();
	String getPassword(); 
	String getUsername(); 

	boolean isAccountNonExpired();  
	boolean isAccountNonLocked();   
	boolean isCredentialsNonExpired(); 
	boolean isEnabled();               
}

6) AuthenticationManager → ProviderManager → AuthenticationProvider

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/unpwd/daoauthenticationprovider.png

  1. UsernamePasswordAuthenticationToken(Authentication)이 AuthenticationManager에게 전달되고 ProviderManager로 위임된다.
  2. ProviderManager가 DaoAuthenticationProvider로 Authentication을 넘긴다.
  3. DaoAuthenticationProvider는 UserDetailsService에서 UserDetails를 조회한다.
  4. 조회된 UserDetails의 password를 증명하기위해 PasswordEncoder를 사용한다.
  5. 인증이 완료 되면 UserDetails을 가진 Authentication이 되고 이것을 다시 AuthenticationManager에게 넘기고, 이것을 SecurityContextHolder에 저장한다.

4. 인증 실패 시

  • SecurityContextHolder가 지워지고 AuthenticationFailureHandler가 호출

정리

유저 로그인 -> UsernamePasswordAuthenticationFilter진입 
-> UsernamePasswordAuthenticationToken 생성(Authentication 타입) 
-> AuthenticationManager에게 인증되지 않은 Authentication 전달
-> AuthenticationManager가 ProviderManager에게 인증 위임
-> ProviderManager가 적절한 AuthenticationProvider를 찾아서 Authentication을 전달
-> DaoAuthenticationProvider(form 로그인 시)가 UserDetails 조회 후 인증이 완료되면 UserDetails의 principal을
Authentication에 추가해 인증된 Authentication을 만듦
-> AuthenticationManger가 UsernamePasswordAuthenticationFilter에 Authentication을 전달
-> SecurityContextHolder에 Authentication 저장.
-> 여러 과정을 거쳐 AuthenticationSuccessHandler 호출
profile
집중

0개의 댓글