본문 바로가기
SpringBoot

[Spring Boot][Error] BCryptPasswordEncoder

by 청양호박이 2019. 12. 27.

Spring Security로 회원가입 / 로그인 / 로그아웃을 구현할때 BCryptPasswordEncoder를 사용했었습니다. 그때, DB에 Password를 암호화해서 저장하고... 실제로 로그인 시도가 있을때 이를 비교해서 인증된 사용자 인지 아닌지를 판단해서 그 이후 로직을 처리했습니다.

 

그때, 고려했던 방법은 아래의 2가지 였습니다. 그 중에 뒤에방식을 사용했었는데요... 왜 그렇게 구현했는지 실제로 눈으로 확인해 보겠습니다.

 

  • Form으로 입력된 Password를 암호화 하여, DB의 암호화 된 Password와 equals로 비교하여 인증
  • Form으로 입력된 Password를 DB의 암호화 된 Password와 자체 제공하는 메서드를 통해서 비교하여 인증

 

1. equals( )


실제로 DB에서 Password를 가져오는 부분은 MyUserDetailsService에서 가져오지만 실질적인 검증에 대한 부분은 MyAuthenticationProvider에서 구현합니다. equals( )로 구현한 소스를 살펴보면...

 

[MyAuthenticationProvider.java]

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

	@Autowired
	MyUserDetailsService myUserDetailsService;
	
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		String loginId = (String) authentication.getPrincipal();
		String loginPass = (String) authentication.getCredentials();
		
		System.out.println(loginId);
		System.out.println(loginPass);
		System.out.println(authentication);
		
		MyUserDetails mud = (MyUserDetails) myUserDetailsService.loadUserByUsername(loginId);
		
		BCryptPasswordEncoder passEncoder = new BCryptPasswordEncoder();
		
		String encryptPass = passEncoder.encode(loginPass);
		System.out.println(encryptPass);
		if(mud == null || !mud.getPassword().equals(encryptPass)) return null;
		
		//권한 가져오는 로직
		ArrayList<String> userRole = myUserDetailsService.getUserRoleByUserId(mud.getUserId());
		ArrayList<GrantedAuthority> authorities = new ArrayList<>();
		for(String eachRole : userRole) {
			authorities.add(new SimpleGrantedAuthority(eachRole));
		}
		
		//(principal, credentials, authorities)
		return new UsernamePasswordAuthenticationToken(loginId, loginPass, authorities);
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return true;
	}
	
}

form에서 입력받은 loginPass는 BCryptPasswordEncoder의 encode 메서드를 사용해서 암호화를 한 후, UserDetails로 정의된 DTO를 통해 받아온 정보를 ... mud.getPassword().equals(encryptPass)와 비교해서 True일 경우 인증을 허가합니다. 

 

실제로 실행하여 localhost/home을 통해서 로그인을 수행하면 단 한번도 성공할 수 없습니다. 비록 정확한 password를 입력한다 해도 말이죠...!!

 

그 이유는 무엇일까요?? 그래서 console에 sysout을 찍어보았습니다. 

위의 내용을 확인해 보면, 로그인을 할때마다 동일한 Password에 대해서 ... 암호화한 결과가 다르게 발생합니다.

 

try 1 : $2a$10$/2YfxB7Vn9fyGDqtB.LtpeE2HB/J30po9sljeL9oQD7p1x1kAK1HS

try 2 : $2a$10$Z8jJpbYEBQESQ8l87XbP1.f9pWoICZyjb3Idx/g59ACQ83yEh8MM2

 

이러니 실패를 할 수밖에 없습니다. 그렇다면 어떻게 해야할까요??

 

 

2. matches( )


이번에는 BCryptPasswordEncoder의 다른 메서드를 확인해 보겠습니다.

보면... Verify the encoded password obtained from storage matches the submetted raw password after it too is encodes. DB에 저장된 암호화 된 password와 form을 통해서 받은 plain password를 비교해 주는 아주 딱 맞는 메서드 입니다. 

 

[MyAuthenticationProvider.java]

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

	@Autowired
	MyUserDetailsService myUserDetailsService;
	
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		String loginId = (String) authentication.getPrincipal();
		String loginPass = (String) authentication.getCredentials();
		
		System.out.println(loginId);
		System.out.println(loginPass);
		System.out.println(authentication);
		
		MyUserDetails mud = (MyUserDetails) myUserDetailsService.loadUserByUsername(loginId);
		
		BCryptPasswordEncoder passEncoder = new BCryptPasswordEncoder();		
		Boolean matchPass = passEncoder.matches(loginPass, mud.getPassword());
		
		if(mud == null || !matchPass) return null;
		
		//권한 가져오는 로직
		ArrayList<String> userRole = myUserDetailsService.getUserRoleByUserId(mud.getUserId());
		ArrayList<GrantedAuthority> authorities = new ArrayList<>();
		for(String eachRole : userRole) {
			authorities.add(new SimpleGrantedAuthority(eachRole));
		}
		
		//(principal, credentials, authorities)
		return new UsernamePasswordAuthenticationToken(loginId, loginPass, authorities);
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return true;
	}
	
}

Boolean으로 matches( )의 결과를 저장하고 True일 경우 인증을 정상적으로 수행합니다. 그럼 결과를 살펴보면... 역시나 정상적으로 실행됩니다.

 

이 부분은 Spring Security 3번째 글에서 확인을 했었습니다. 그럼 즐거운 개발되세요~~

 

-Ayotera Lab-

댓글