Java Spring Boot(7)

제이 용·2025년 11월 17일

암호화 하기

예외처리에서 중요하다고 생각하는 부분은 기존의 사용자가 설정한 비밀번호를 암호화 하는 것이라고 생각한다.

일단 전혀 지식이 없기에 구글링을 먼저 해보았다.

Spring Boot에 암호화 하는 방법은 꽤나 있었고, 그 중 요구사항에 따라 먼저 직접 적용시켜보기로 했다.

다만 처음보는 코드이기에 해석을 먼저 해보기로 했다.

암호화 코드

import at.favre.lib.crypto.bcrypt.BCrypt;   // BCrypt 라이브러리를 불러와서 비밀번호 암호화 기능을 사용
import org.springframework.stereotype.Component; // 스프링이 이 클래스를 빈으로 관리할 수 있게 해주는 어노테이션

@Component // 스프링 컨테이너가 이 클래스를 자동으로 빈 등록하도록 설정
public class PasswordEncoder {

    public String encode(String rawPassword) { // 사용자가 입력한 평문 비밀번호를 암호화하는 메서드
        return BCrypt.withDefaults() // 기본 설정을 사용하여 BCrypt 객체 생성
                .hashToString(BCrypt.MIN_COST, rawPassword.toCharArray()); 
                // MIN_COST는 암호화 강도 중 가장 빠른 옵션을 사용
                // rawPassword를 char 배열로 바꿔 bcrypt로 해시한 후 문자열 형태로 반환
    }

    public boolean matches(String rawPassword, String encodedPassword) { 
        // rawPassword: 사용자가 로그인 시 입력한 평문 비밀번호
        // encodedPassword: DB에 저장된 암호화된 비밀번호

        BCrypt.Result result = BCrypt.verifyer() 
                .verify(rawPassword.toCharArray(), encodedPassword);  
                // rawPassword를 char 배열로 변환
                // encodedPassword와 일치하는지 bcrypt 알고리즘으로 비교

        return result.verified; // 검증 결과(true/false)를 반환
    }
}

BCrypt가 뭘까?

  • 비밀번호를 안전하게 저장하기 위해 사용하는 해시 알고리즘
  • 비밀번호를 복호화할 수 없도록 단방향으로 암호화해주는 기술

쓰이는 이유

1) 절대 원래 비밀번호로 되돌릴 수 없음

  • SHA-256 같은 일반 해시는 너무 빠르게 계산되기 때문에 무차별 대입 공격(Brute-force)에 취약하다.
  • BCrypt는 "고의로 느리게" 만들어져 있어서 공격이 어렵다.

2) Salt 자동 적용

  • 같은 비밀번호라도 매번 다른 결과가 나오게 만드는 랜덤 데이터(salt)를 자동으로 붙여준다.

3) 강도(cost factor)를 조절할 수 있음

  • BCrypt의 cost 값을 올릴수록 암호화가 느려지지만 더 안전해진다.

요약

비밀번호 저장용으로 특화된 해시 알고리즘

salt 자동 포함

매번 결과가 달라서 안전

cost로 보안 강도 조절 가능

복호화 불가능(단방향)

암호화 코드 해석

  • encode(비밀번호) 메서드 : 비밀번호를 암호화한다.
  • public boolean matches(비밀번호, 암호화된 비밀번호) 메서드 : 비밀번호를 암호화 했을 때 DB에 저장된 암호화된 비밀번호와 비교하여 참과 거짓을 반환한다.

따라서 비밀번호 생성 시 암호화된 비밀번호를 저장하고, 특정 비밀번호를 요구하는 API를 실행할 때 사용자가 생성할 때 쓴 비밀번호의 암호화 코드와, 요구한 비밀번호의 암호화 코드가 일치하면 실행이 되게끔 작동된다는 것이다.


결론

회원가입 시 인코드를 통해 암호화를 적용을 시키고, 요구하는 해당 서비스의 조건문을 암호화 비교로 바꾼다.

기존

User user = new User(request.getUserName(), request.getEmail(), request.getPassword());

수정

if (!user.getPassword().equals(request.getPassword())) {
    throw new IllegalArgumentException("비밀번호가 올바르지 않습니다.");
}

기존

User user = new User(request.getUserName(), request.getEmail(), request.getPassword());

수정

if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
    throw new IllegalArgumentException("비밀번호가 올바르지 않습니다.");
}

잘 작동한다.


기존 코드에 기능 추가하기

댓글 기능 추가

구상

Comment 엔티티 만들기
Repository 만들기
DTO 만들기
Service 로직 작성
Controller 만들기
권한 검증(service) 추가
Validation 적용

  • 각 클래스별 주의사항

Comment 엔티티 만들기

  • 일정에 달리는 댓글이기에 연관관계 추가와 사용자의 비밀번호와 매치, 유저 닉네임 또한 필요하기에 사용자와도 연관관계가 있어야한다.
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "schedule_id")
    private Schedule schedule;

Repository 만들기

  • 모든 스캐줄을 Read하는 기능을 위해 레퍼지토리에 메서드를 추가해주어야한다.
List<Comment> findAllByScheduleId(Long scheduleId);

Service 로직 작성

  • 스캐줄과 유저의 레퍼지토리를 모두 포함한다.(속성)
  • userName을 응답할때 User의 연관관계를 활용하여 User로부터 name을 끌고온다.
   	private final CommentRepository commentRepository;
    private final ScheduleRepository scheduleRepository;
    private final UserRepository userRepository;
    
    --------------------------------------------
    
    saved.getId(),
    saved.getUser().getUserName(),

Controller 만들기

  • 요청과 응답에 형식이 맞게 작성을 해주어야한다.
public ResponseEntity<Void> delete(
            @SessionAttribute(name = "loginUser", required = false) SessionUser sessionUser,
            @PathVariable Long commentId)
            
-------------------------------------------------

commentService.delete(commentId, sessionUser.getId());

작성간 유의사항으로는 모두 만들고나서 오류가 났었는데 그 이유는 레퍼지토리에 들어가있는 메서드의 형식이 어긋나 있었다.

List<Comment> findAllbyScheduleId(Long scheduleId);

위와 같이 중간에 대문자 B가 아닌 b가 들어가 작동하지 않는 현상을 보았고, 카멜형식을 제대로 지켜야겠다는 생각이 들었다..

0개의 댓글