username만 매칭되면 user 세션 생성 되는 문제 해결 - AuthenticationProvider를 통한 password 기반 인증
Spring

username만 매칭되면 user 세션 생성 되는 문제 해결 - AuthenticationProvider를 통한 password 기반 인증

발생한 문제 : DB에 존재하는 user의 username(아이디)만 일치하면 user session이 생성되어 index page에서 로그인 처리가 되는 문제.

 

 

원인 : UserDetailsService를 구현한 클래스의 loadUserByUsername 메서드에서 username을 통한 user의 존재여부만을 판단하고, 존재 시 user 세션을 생성하면서 password에 대한 인증이 없어 발생한 문제이다.

 

@RequiredArgsConstructor
@Service
public class PrincipalUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    	// UserRepository를 통해 username에 해당하는 user의 존재여부만 판단
        User user = userRepository.findByUsername(username).orElseThrow(() ->
                new UsernameNotFoundException("사용자를 찾을 수 없습니다."));

        // password 일치여부와 무관하게 username에 해당하는 user 존재 시 session을 생성
        httpSession.setAttribute("user", SessionUser.fromEntity(user));

        return new PrincipalUserDetails(user);
    }
}

 

 

해결 : AuthenticationProvider를 구현하고 해당 provider를 Configuration에 등록하여 username, password 기반의 인증을 수행하도록 한다.

 

@RequiredArgsConstructor
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final PrincipalUserDetailsService principalUserDetailsService;
    private final BCryptPasswordEncoder passwordEncoder;
    private final HttpSession httpSession;

    @Override
    public Authentication authenticate(Authentication authentication) 
    			throws AuthenticationException {

        // Authentication 객체에서 username과 password를 꺼낸다.
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // UserDetailsService에서 UserDetails를 꺼낸다.
        PrincipalUserDetails userDetails =
                (PrincipalUserDetails) principalUserDetailsService.loadUserByUsername(username);

        // UserDetails의 password와 Authentication 객체의 password를 비교한다.
        if (!this.passwordEncoder.matches(password, userDetails.getPassword())) {
            throw new BadCredentialsException("아이디 또는 비밀번호가 일치하지 않습니다.");
        }

        // username, password 일치 시 세션에 user를 저장한다.
        httpSession.setAttribute("user", SessionUser.fromEntity(userDetails.getUser()));
		
        // username, password 일치 시 토큰을 생성한다.
        return new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
    }

    // 커스터마이징한 Authentication Token을 사용하지 않으므로 supports true
    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

 

1. Authentication 객체에서 getName(), getCredentials()를 통해 사용자가 입력한 username과 password를 꺼내온다.

2. UserDetailsService에서 loadUserByUsername()을 통해 UserDetails를 가져온다.

3. 사용하는 passwordEncoder (본 코드에서는 BCryptPasswordEncoder)의 matches 메서드를 통해 사용자가 입력한 원본 password와 encoding된 UserDetails의 password를 비교한다.

3-1. password가 일치하지 않을 시 throw Exception을 통해 로그인 실패에 대한 처리 (이후 외부에서 해당 Exception Handling)

3-2. password가 일치할 시 세션에 user를 저장한다.

 

 

** 해당 AuthenticationProvider를 WebSecurityConfigurerAdapter의 configure에서 추가해주어야 한다!

** AuthenticationManagerBuilder를 parameter로 가지는 configure 메서드를 오버라이드 하여 추가한다.

// SecurityConfig.java

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final AuthenticationProvider authenticationProvider;

    // CustomAuthenticationProvider 설정
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider);
    }
    
	...
}

 

결과 : 

 

username이 일치하더라도 password가 일치하지 않아 로그인 실패 시 세션이 생성되지 않는다.

username과 password가 모두 일치할 때에만 세션이 생성된다.

 

소스코드 : https://github.com/HunSeongPark/lolcruit

 

GitHub - HunSeongPark/lolcruit: 롤크루트 - 리그오브레전드 라이너 채용공고 게시판 🎲

롤크루트 - 리그오브레전드 라이너 채용공고 게시판 🎲. Contribute to HunSeongPark/lolcruit development by creating an account on GitHub.

github.com