calendar

Bcrypt 암호화적용

로드존슨 2023. 3. 17. 23:39
728x90

스프링 시큐리티로 패스워드 암호화가 있지만 가장 기본적인 Bcrypt 를 적용해보고 시큐리티 적용하는것도 같이 적어본다. 

 

Bcrypt (따로 라이브러리 추가 없이 메소드로만 진행된다)

1)Encryptor 인터페이스를 만든다.

  -수정과 관리가 용이하기 위해 인터페이스를 만들었고 encrypt , isMatch 메소드를 만든다.

 

2)BCryptEncryptor 클래스에서 Encryptor 를 구현한다. 

 

3)UserService 클래스에 password 유효검증에 적용한다.  Encryptor 를 통해 암호화모듈로 저장하고, 이를 해석한다.

    그리고 UserService  에서 user를 저장하는 메소드에 암호화모듈을 적용하여 저장한다.

    특이한점은 User 에 있는 isMatched 메소드이다.  스트래티지 패턴을 적용하여 유효검증을 하였느데 

    BCryptEncryptor 에 위임하여 유효성검증을 넘겼다.

(스트래티지 패턴 : 행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀수 있게 해주는 패턴)

->Encryptor 클래스로 캡슐화 하여 동적으로 해결한다. Encryptor 는 인터페이스라서 구현체가 BCryptEncryptor 되어있지만 암호화모듈 변경시 유지관리 및 확이 용이하여 이러한 패턴이 적용하였다.

한마디로, 로직을 구현하는 구현클래스 말고  인터페이스를  통해 알로리즘을 캡슐화한다.  

public interface Encryptor {
    //interface 를 자주 사용한다. 암호화모듈이 BCryptEncryptor 있을수도 있고
    //여러가지모듈이 있는데, 필요할때 손쉽게 바꿀수 있게 interaface 적용


    String encrypt(String origin);
    //origin 문자를 받아서 암호화한 해쉬문자열을 반환하는 메소드

    boolean isMatch(String origin, String hashed);
    //해쉬된 문자와 ,origin 과 매치해서 맞는가 확인하는 메소드
}
public class BCryptEncryptor implements Encryptor {
    @Override
    public String encrypt(String origin) {
        return BCrypt.hashpw(origin, BCrypt.gensalt());
    }

    @Override
    public boolean isMatch(String origin, String hashed) {
        try {
            return BCrypt.checkpw(origin, hashed);
        } catch (Exception e) { // 여러 예외가 있다.
            return false;
        }
    }
}

 

@Service
@RequiredArgsConstructor
public class UserService {

    private final Encryptor bcryptEncryptor;

    private final UserRepository userRepository;

    @Transactional
    public User create(UserCreateReq req) {

        userRepository.findByEmail(req.getEmail())
                .ifPresent(u -> {
                    throw new RuntimeException("cannot find user");
                });


        return userRepository.save(User.builder()
                .name(req.getName())
//                .password(req.getPassword())
                .password(bcryptEncryptor.encrypt(req.getPassword()))
                .email(req.getEmail())
                .birthday(req.getBirthday())
                .build());
    }


    @Transactional
    public Optional<User> findPwMatchUser(String email, String password) {
        return userRepository.findByEmail(email)
                .map(u -> u.isMatched(bcryptEncryptor, password) ? u : null);
    }


}
@Table(name = "users")
public class User extends BaseEntity {

                   .
                   .
                   .
                   .
                   .
                 

public boolean isMatched(Encryptor encryptor, String pw) {
        return encryptor.isMatch(pw, this.password);
    }

시큐리티 (아래문구를 그~대로 쓰고 메소드하나로 끝)

-로그인 양식을 통해 비밀번호가 제출되면 spring security의 필터 체인은 구성된 암호화 알고리즘을 비밀번호 값에 적용하여 암호화하여 서버로 전송합니다. 암

@Bean // 반드시 PasswordEncoder 등록을 해야 한다. 스프링 시큐리티에서 제공하는 암호화모듈을 활용할 수 있다.
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
        //PasswordEncoderFactories 로부터 위임해서 passwordEncoder로 가지고 오겠다 라는 뜻
    }
728x90