티스토리 뷰
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;
}
}
참고
728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- Kotlin
- Spring Security
- intellij
- maven
- LocalDateTime
- k8s
- oracle
- 베리 심플
- svn
- docker
- Github Status
- jQuery
- mybatis
- Bash tab
- Spring
- config-location
- localtime
- JavaScript
- mybatis config
- 오라클
- elasticsearch
- Mac
- input
- 북리뷰
- Java
- rocky
- Linux
- LocalDate
- window
- springboot
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함