인프런 커뮤니티 질문&답변

작성자 없음

작성자 정보가 삭제된 글입니다.

호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)

회원가입과 비밀번호 암호화-3

안녕하세요 유틸클래스 질문있습니다

해결된 질문

작성

·

1K

·

수정됨

2

보통 암호화하는 클래스들을 static 유틸클래스들로 만들었었는데 이런 static유틸클래스와 @Component를 달아서 사용하는 유틸클래스는 어떤 차이를 만들어낼수있는건가요..?

제가 생각했을땐 유틸클래스를 스프링 빈으로 관리하겠다는 생각만 떠오르는데.. 이해할수있을만한 예시가있을까요..? 제가 못찾는건지 마땅히 그럴싸한 자료를 못봤던것같아서요 ㅠ

강의도중 @Component얘기가 잠깐나와서 생각이나서 질문드려봅니다...

답변 2

1

호돌맨님의 프로필 이미지
호돌맨
지식공유자

안녕하세요. 호돌맨입니다.
질문을 남겨주셔서 감사합니다.

코드에서 보여드린 부분은 꼭 @Component로 만들지 않아도 되서 뺐습니다. 저라면 추가할것 같습니다.

그래서 @Component와 별개로 static vs 일반 클래스 정도로 생각해보면 좋을것 같습니다.

우선 저라면 테스트 코드를 위해서 그렇게 할 것 같습니다.

현재 회원가입시 요청보낸 비밀번호 1234와 실제 DB에 들어가 있는 암호화비밀번호 값이 다르기 때문에 테스트 케이스 검증이 까다로웠습니다.

@Test
@DisplayName("회원가입 성공")
void test1() {
    // given
    Signup signup = Signup.builder()
            .email("hodolman88@gmail.com")
            .password("1234")
            .name("호돌맨")
            .build();

    // when
    authService.signup(signup);

    // then
    User user = userRepository.findAll().iterator().next();
    assertNotNull(user.getPassword());
    assertNotEquals("1234", user.getPassword());
}

현재는 user.getPassword()가 null이 아니고 요청 보냈던 1234가 아닌지 만 검증한 찝찝한 상황이죠. 이를 정확하게 하려면 우리가 PasswordEncoder를 통해 암호화 한 암호화비밀번호가 맞는지를 확인해야합니다.

저는 아래와 같은 작업을 하겠습니다.

  1. PasswordEncoder.java -> ScryptPasswordEncoder.java 파일명 변경, @Component 추가

     

  2. PasswordEncoder.java interface 생성

    public interface PasswordEncoder {
    
        String encrypt(String rawPassword);
        boolean matches(String rawPassword, String encryptedPassword);
    }

     

  3. ScryptPasswordEncoder.java에 PasswordEncoder interface 구현 (우리는 벌써 구현되어 있겠죠)

    @Component
    public class ScryptPasswordEncoder implements PasswordEncoder {
    
        private static final SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(
                16,
                8,
                1,
                32,
                64);
    
        @Override
        public String encrypt(String rawPassword) {
            return encoder.encode(rawPassword);
        }
    
    
        @Override
        public boolean matches(String rawPassword, String encrpytedPassword) {
            return encoder.matches(rawPassword, encrpytedPassword);
        }
    
    }

    그러면 최종적으로 위와 같은 모습이 됩니다. (참고로 PasswordEncoder는 스프링에서도 제공하는 클래스이기 때문에 import에 유의 해야합니다.)

     

  4. 그러면 기존에 AuthService에서 만들었던 PasswordEncoder encoder = new PasswordEncoder()를 아래와같이 주입받는 방식으로 변경할 수 있습니다.

    @Service
    @RequiredArgsConstructor
    public class AuthService {
    
        private final PasswordEncoder passwordEncoder;
    
        public void signup(Signup signup) {
            // ...
            String encryptedPassword = passwordEncoder.encrypt(signup.getPassword());
    
            var user = User.builder()
                    .password(encryptedPassword)
            userRepository.save(user);
        }
    }

 

이렇게되면 우리는 테스트 시 PasswordEncoder를 Scrypt로 넘길지 혹은 다른거로 넘길지 결정 할 수 있게됩니다.

@SpringBootTest
class AuthServiceTest {

    //@Autowired
    //private AuthService authService;

    @Test
    @DisplayName("회원가입 성공")
    void test1() {
        // given
        Signup signup = Signup.builder()
                .email("hodolman88@gmail.com")
                .password("1234")
                .name("호돌맨")
                .build();

        AuthService authService = new AuthService(mockUserRepository, mockPasswordEncoder);
        
        // when
        authService.signup(signup);

}

현재는 테스트코드에서 AuthService를 @Autowired로 주입받고 있으나 위와 같이 mock객체를 넘겨 테스트 할 수도 있습니다. 다만 저희는 실제 애플리케이션 동작을 테스트 하고 있으므로 mock을 통해 AuthService를 만들어내는 것 보다 TestPasswordEncoder를 만들어내어 주입할 수 있겠죠.

 

우리는 @Profile별로 테스트가 가능하도록 만들 수 있습니다.

@TestComponent
public class TestPasswordEncoder implements PasswordEncoder {

    @Override
    public String encrypt(String rawPassword) {
        return rawPassword;
    }

    @Override
    public boolean matches(String rawPassword, String encrpytedPassword) {
        return rawPassword.equals(encrpytedPassword);
    }
}

위와 같이 Test를 위한 PasswordEncoder를 따로 만든 뒤 @TestComponent를 추가합니다.

 

@Profile("default")
@Component
public class ScryptPasswordEncoder implements PasswordEncoder {
}

기존 ScryptPasswordEncoder를 위 처럼 @Profile("default")일때만 등록되게 어노테이션을 추가합니다.

 

@ActiveProfiles("test")
@SpringBootTest
@Import(TestPasswordEncoder.class)
class AuthServiceTest {
}

그리고 AuthServiceTest에 @ActiveProfile("test")를 추가하여 test profile로 실행되게 만들면 AuthService내에 PasswordEncoder가 주입될때 자동으로 @TestPasswordEncoder가 주입되게 만들 수 있습니다.

 

@Test
@DisplayName("회원가입 성공")
void test1() {
    // given
    Signup signup = Signup.builder()
            .email("hodolman88@gmail.com")
            .password("1234")
            .name("호돌맨")
            .build();

    // when
    authService.signup(signup);

    // then
    assertEquals(1, userRepository.count());

    User user = userRepository.findAll().iterator().next();
    assertEquals("1234", user.getPassword());
}

그러면 PasswordEncoder 인터페이스 구현체 (TestPasswordEncoder)를 통해서 비밀번호가 평문으로 잘 들어갔는지 확인할 수 있게됩니다.

이거 관련해서는 추가 영상을 만들도록 하겠습니다.

감사합니다.

0

호돌맨님의 프로필 이미지
호돌맨
지식공유자

아, 그리고 참고하시면 좋은 링크

감사합니다.

작성자 없음

작성자 정보가 삭제된 글입니다.

질문하기