티스토리 뷰

728x90

인증에 대한 전반적인 처리과정 흐름을 이해해보자

  • 클라이언트가 로그인을 요청한다.
  • 폼 인증방식으로 요청할 때 UsernamePasswordAuthenticationFilter가 작동한다.
  • 사용자의 id와 password를 받아서 Authentication 객체에 담는다.
  • id와 password를 담은 인증 전 토큰 객체를 생성해서 AuthenticationManager에게 인증을 맡긴다.
  • AuthenticationManager가 필터로 부터 받은 인증객체를 전달받는다.
  • AuthenticationManager의 역할은 인증의 전반적인 관리를 하는데 실제로 인증 역할을 하지 않고 적절한 AuthenticationProvider에 위임한다.
  • AuthenticationManager가 사용자의 현재 id , password가 일치하는지 여부를 확인하지 않는다. 이 클래스는 내부적으로 List 변수에는 한 개 이상의 AuthenticationProvider를 담고 있는데 그 객체들 중에서 현재 인증에 사용될 수 있는 Provider를 찾아서 그 Provider에게 인증을 위임하는 역할을한다.
  • AuthenticationProvider 가 실제적으로 ID, Password와 같은 정보를 맞는지 검증하는 역할을 한다.
  • id를 전달하면서 User객체를 전달한다.
  • 요청할 때는 UserDetailsService 라는 인터페이스에게 전달한다. loadUserByUsername(username) 요청
  • Repository에서 유저 객체를 조회해서 만약 있다면 UserDetails 타입으로 반환한다.
  • 예외가 발생하면 UsernamePasswordAuthenticationFilter가 예외를 받아서 처리하게 된다.
  • AuthenticationProvider (DaoAuthenticationProvider)는 이제 Password 검증을 시작한다. 인증객체의 Password와 반환받은 UserDetails의 Password를 비교한다.
  • 일치하지 않을 경우 BadCredentialException 발생 후 인증 실패
  • 성공한 인증객체(UserDetals와 authorities를 담은 인증 후 토큰 객체 Authentication)를 UsernamePasswordAuthenticationFilter에 전달한다.

DaoAuthenticationProvider 는 폼 인증방식의 인증처리를 담당하는 AuthenticationProvider 구현체이다.

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

	/**
	 * The plaintext password used to perform
	 * {@link PasswordEncoder#matches(CharSequence, String)} on when the user is not found
	 * to avoid SEC-2056.
	 */
	private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

	private PasswordEncoder passwordEncoder;

	/**
	 * The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
	 * on when the user is not found to avoid SEC-2056. This is necessary, because some
	 * {@link PasswordEncoder} implementations will short circuit if the password is not
	 * in a valid format.
	 */
	private volatile String userNotFoundEncodedPassword;

	private UserDetailsService userDetailsService;

	private UserDetailsPasswordService userDetailsPasswordService;

	public DaoAuthenticationProvider() {
		setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
	}

	@Override
	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		String presentedPassword = authentication.getCredentials().toString();
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

	@Override
	protected void doAfterPropertiesSet() {
		Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
	}

	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

	@Override
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
			UserDetails user) {
		boolean upgradeEncoding = this.userDetailsPasswordService != null
				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
		if (upgradeEncoding) {
			String presentedPassword = authentication.getCredentials().toString();
			String newPassword = this.passwordEncoder.encode(presentedPassword);
			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
		}
		return super.createSuccessAuthentication(principal, authentication, user);
	}

	private void prepareTimingAttackProtection() {
		if (this.userNotFoundEncodedPassword == null) {
			this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
		}
	}

	private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
		if (authentication.getCredentials() != null) {
			String presentedPassword = authentication.getCredentials().toString();
			this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
		}
	}

	/**
	 * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
	 * not set, the password will be compared using
	 * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
	 * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
	 * types.
	 */
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
		this.passwordEncoder = passwordEncoder;
		this.userNotFoundEncodedPassword = null;
	}

	protected PasswordEncoder getPasswordEncoder() {
		return this.passwordEncoder;
	}

	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	protected UserDetailsService getUserDetailsService() {
		return this.userDetailsService;
	}

	public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
		this.userDetailsPasswordService = userDetailsPasswordService;
	}

}

참고

https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0

스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의

초급에서 중.고급에 이르기까지 스프링 시큐리티의 기본 개념부터 API 사용법과 내부 아키텍처를 학습하게 되고 이를 바탕으로 실전 프로젝트를 완성해 나감으로써 스프링 시큐리티의 인증과

www.inflearn.com

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함