<aside> 💡 서울과학기술대학교 GDG on Campus: SeoulTech 4TH Backend 세션 자료입니다.
</aside>
레포지토리는 생성된 데이터베이스 테이블의 데이터들을 저장, 조회, 수정, 삭제할 수 있도록 도와주는 인터페이스이다. JpaRepository는 JPA가 제공하는 인터페이스 중 하나로 CRUD 작업을 처리하는 메서드들을 이미 내장하고 있다.
CRUD는 Create, Read, Update, Delete의 앞글자만 따 만든 단어로, 데이터 처리의 기본 기능을 의미한다.
@Repository : 스프링이 레포지토리로 인식하게 만들어주는 애너테이션
// user/repository/UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
//TODO: 페이징,검색 기능 추가
}
User 서비스에는 User 리포지터리를 사용하여 회원을 생성하는 signup 메서드를 추가했다. 이떄 User의 비밀번호는 보안을 위해 반드시 암호화하여 저장해야 한다. 그래서 PasswordEncoder를 구현하여 암호화하여 비밀번호를 저장했다(바로 다음에 작성 예정)
@RequiredArgsConstructor : final 변수를 모두 포함하는 생성자를 만들어주는 애너테이션
@Transactional : DB와 동시 연결하는 요청을 한개로 제한해주는 애너테이션. 동시성 문제 해결
// user/service/UserService.java
package gdsc.session.user.service;
import gdsc.session.user.dto.UserInfo;
import gdsc.session.util.PasswordEncoder;
import gdsc.session.user.domain.User;
import gdsc.session.user.dto.LoginRequest;
import gdsc.session.user.dto.SignupRequest;
import gdsc.session.user.exception.AlreadyExistsEmailException;
import gdsc.session.user.exception.PasswordNotMatchException;
import gdsc.session.user.exception.UserNotFound;
import gdsc.session.user.repository.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Objects;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Transactional
public void signup(SignupRequest signupRequest) {
Optional<User> userOptional = userRepository.findByEmail(signupRequest.getEmail());
String encryptedPassword = getEncryptedPassword(signupRequest.getEmail(),signupRequest.getPassword1());
User user = User.builder()
.email(signupRequest.getEmail())
.password(encryptedPassword)
.username(signupRequest.getUsername())
.build();
userRepository.save(user);
}
//@Transactional(readOnly = true)
//public UserInfo login(LoginRequest loginRequest) {
// User user = userRepository.findByEmail(loginRequest.getEmail()).get();
// return UserInfo.builder()
// .id(user.getId())
// .email(user.getEmail())
// .username(user.getUsername())
// .build();
//}
private String getEncryptedPassword(String email, String password) {
return passwordEncoder.encode(email, password);
}
}
앞서 얘기했듯이 비밀번호는 데이터베이스에 암호화되어 저장되어야 한다. 그래서 비밀번호를 암호화하는 클래스를 생성했다.(중요한 내용 아니므로 복붙)
@Component : 스프링 빈으로 생성하는 애너테이션
// utils/PasswordEncoder.java
@Component
public class PasswordEncoder {
public String encode(String email, String password) {
try {
KeySpec spec = new PBEKeySpec(password.toCharArray(), getSalt(email), 85319, 128);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = factory.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException |
InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
// 비밀번호가 같은 사용자끼리 암호화한 값이 같으므로 이메일을 이용해 솔트값 생성
// 사용자별로 암호화된 비밀번호 값이 다르다
private byte[] getSalt(String email)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] keyBytes = email.getBytes("UTF-8");
return digest.digest(keyBytes);
}
}
회원 가입을 위한 엔티티와 서비스가 준비되었으니 URL 폼을 위한 컨트롤러를 만들어보자.
@PostMapping(”/signup”)을 통해 /user/signup 으로 들어오는 POST 요청을 signup 메서드가 처리한다. 빈 검증을 도입하고 서비스 코드를 호출하여 회원가입 로직을 진행한다.
@Slf4j : 로그를 남기는 애너테이션
@RestController : API 통신 방식으로 컨트롤러 작성