관리 메뉴

bright jazz music

blog40 : 회원가입과 비밀번호 암호화-3 본문

Projects/blog

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

bright jazz music 2023. 2. 26. 19:11

비밀번호가 평문에서 암호로 바뀌면서 기능하지 않는 것들이 있다.

예를 들면 아래와 같이 회원가입을 한 뒤에 평문을 넣어 로그인을 시도하면 실패한다.

  //AuthServiceTest.java
    @Test
    @DisplayName("로그인 성공")
    void test3(){
        //given
        Signup signup = Signup.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .name("catnails")
                .build();
        authService.signup(signup);

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .build();

        //when
        authService.signin(login);

        //then
    }
}

비밀번호가 암호화 되었는데 평문을 입력하여 테스트 했기 때문이다. 그러면 암호화 된 이후에는 비밀번호 매칭 확인을 어떻게 해야 할까?

 

 

1. AuthService.java 코드 변경

//AuthService.java
    @Transactional
//    public String signin(Login login){
    public Long signin(Login login){

        //        User user = userRepository.findByEmailAndPassword(login.getEmail(), login.getPassword()) 비밀번호가 암호화 되었기 때문에 기능하지 않는다.
        //                .orElseThrow(InvalidSigninInformation::new);

        User user = userRepository.findByEmail(login.getEmail())
                .orElseThrow(InvalidSigninInformation::new);

        SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(16, 8, 1, 32, 64);

        //평문 비번과 인코딩(암호화)된 비번을 넣어 매치하는지 확인한다.
        var matches = encoder.matches(login.getPassword(), user.getPassword());
        if(!matches) {
            throw new InvalidSigninInformation();
        }
//        //세션 발급
//        Session session = user.addSession();

        return user.getId();
    }

평문으로 암호화된 비번을 조회할 순 없다. 따라서 email만 사용해서 사용자가 존재하는지 확인한다. 없으면 InvalidSigninInformation 예외를 발생시킨다.

 

존재하는 경우 SCryptPasswordEncoder의 matches 메소드를 사용하여 평문 비번과 암호화된 비밀번호가 일치하는지 확인한다. 일치하지 않는 경우 InvalidSigninInformation 예외를 발생시킨다. 일치하면 AuthController에 user.getId()를 반환한다.

//AuthServiceTest.java
    @Test
    @DisplayName("로그인 성공")
    void test3(){
        //given
        Signup signup = Signup.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .name("catnails")
                .build();
        authService.signup(signup);

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .build();

        //when
        Long userId = authService.signin(login);

        //then
        Assertions.assertNotNull(userId);
    }
}

테스트 역시 성공

 

 

2. 비밀번호가 틀려서 로그인 실패한 케이스의 테스트 코드 작성

    //AuthServiceTest.java
    @Test
    @DisplayName("로그인 시 비밀번호 틀림")
    void test4(){
        //given
        Signup signup = Signup.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .name("catnails")
                .build();
        authService.signup(signup);

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("5678") //비밀번호 틀림
                .build();

        //expected
        Assertions.assertThrows(InvalidSigninInformation.class,
                () -> authService.signin(login));
    }
}

테스트 통과

 

 

그런데 통과 또는 실패 케이스에서 authService.signup() 메소드를 직접 넣어서 테스트를 하는 것이 좋다고는 할 수 없다. authService.signup()의 내용이 달라지면 테스트 코드 역시 전부 실패하기 때문이다.

 

따라서 이런 방식보다는 User 엔티티를 만들고 userRepository의 메소드를 통해 미리 데이터를 만드는 방식이 낫다. 그런데 이 방법은 비밀번호가 암호화되지 않은 상태로 DB에 저장되어 버린다.

 

이 방법을 해결하기 위해서 SCryptPasswordEncoder를 가지는 클래스 컴포넌트로 만든다. 그러고는 userRepository에 넘기기 전에 사용하여 값을 암호화 하는 것이다.

 

 

3. SCryptPasswordEncoder를 클래스 컴포넌트로 만들어주기

 

//PasswordEncoder.java

package com.endofma.blog.crypto;

import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

public class PasswordEncoder {
    private static final SCryptPasswordEncoder encoder
            = new SCryptPasswordEncoder(16, 8, 1, 32, 64);

    public String encrypt(String rawPassword) {
        return encoder.encode(rawPassword);
    }

    public boolean matches(String rawPassword, String encryptedPassword) {
        return encoder.matches(rawPassword, encryptedPassword);
    }

}

 

4. AuthService.java 코드 변경

기존에 직접 SCryptPasswordEncoder 객체를 생성하던 코드를 PasswordEncoder를 사용하는 코드로 수정한다.

//AuthService.java

package com.endofma.blog.service;

import com.endofma.blog.crypto.PasswordEncoder;
import com.endofma.blog.domain.Session;
import com.endofma.blog.domain.User;
import com.endofma.blog.exception.AlreadyExistsEmailException;
import com.endofma.blog.exception.InvalidRequest;
import com.endofma.blog.exception.InvalidSigninInformation;
import com.endofma.blog.repository.UserRepository;
import com.endofma.blog.request.Login;
import com.endofma.blog.request.Signup;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;


    //AuthService.java
    @Transactional
//    public String signin(Login login){
    public Long signin(Login login){

        //        User user = userRepository.findByEmailAndPassword(login.getEmail(), login.getPassword()) 비밀번호가 암호화 되었기 때문에 기능하지 않는다.
        //                .orElseThrow(InvalidSigninInformation::new);

        User user = userRepository.findByEmail(login.getEmail())
                .orElseThrow(InvalidSigninInformation::new);

//        SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(16, 8, 1, 32, 64);
        PasswordEncoder encoder = new PasswordEncoder();

        //평문 비번과 인코딩(암호화)된 비번을 넣어 매치하는지 확인한다.
        var matches = encoder.matches(login.getPassword(), user.getPassword());
        if(!matches) {
            throw new InvalidSigninInformation();
        }
//        //세션 발급
//        Session session = user.addSession();

        return user.getId();
    }

    //AuthService.java
    //회원가입
    public void signup(Signup signup) {

        //email 중복체크
        Optional<User> userOptional = userRepository.findByEmail(signup.getEmail());
        if(userOptional.isPresent()) {
            throw new AlreadyExistsEmailException();
        }

        //비밀번호 암호화 (cpuCost, memoryCost, parallelization, keyLength, saltLength)
        //보통 salt는 uuid를 생성하여 65바이트로 넣어준다. 이 라이브러리는 내부적으로 하는 모양
//        SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(16, 8, 1, 32, 64);
        PasswordEncoder encoder = new PasswordEncoder();
        String encryptedPassword = encoder.encrypt(signup.getPassword());


        //엔티티로 변환
        var user = User.builder()
                .name(signup.getName())
                .password(encryptedPassword)
                .email(signup.getEmail())
                .build();

        userRepository.save(user);
    }
}

 

5. AuthServiceTest.java 코드 변경

 

   //AuthServiceTest.java
    @Test
    @DisplayName("로그인 성공")
    void test3(){
        //given
        PasswordEncoder encoder = new PasswordEncoder();
        String encryptedPassword = encoder.encrypt("1234");

        User user = User.builder()
                .email("catnails@gmail.com")
                .password(encryptedPassword)
                .name("catnails")
                .build();

        userRepository.save(user);
//        authService.signup(signup);

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .build();

        //when
        Long userId = authService.signin(login);

        //then
        Assertions.assertNotNull(userId);
    }

    //AuthServiceTest.java
    @Test
    @DisplayName("로그인 시 비밀번호 틀림")
    void test4(){
        //given
        PasswordEncoder encoder = new PasswordEncoder();
        String encryptedPassword = encoder.encrypt("1234");

        User user = User.builder()
                .email("catnails@gmail.com")
                .password(encryptedPassword)
                .name("catnails")
                .build();

        userRepository.save(user);

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("5678") //비밀번호 틀림
                .build();

        //expected
        Assertions.assertThrows(InvalidSigninInformation.class,
                () -> authService.signin(login));
    }
}

테스트 통과

 

Comments