User targetUser = userRepository.findById(user.getId())
.orElseThrow(() -> new IllegalArgumentException("회원이 존재하지 않습니다")); for (Password passwordHistory : passwordHistoryList) {
if (passwordEncoder.matches(password, passwordHistory.getUpdatedPassword())) {
throw new IllegalArgumentException("이미 3회 이내 변경되었던 비밀번호입니다.");
}
}3개의 비밀번호를 기억해서 그것들의 사용 제한을 하고 싶은건데, 3개를 저장하지 못한다
key-value 데이터를 사용하는 redis는 값을 한 개만 저장하고 새로운 값이 들어오면 이미 저장된 한 개의 값에 덮어 씌워진다 = 여러 값을 저장하지 못함 = 과거 3개의 비밀번호 값을 저장하지 못함
DB로 변경!!!!!!!!!!!!!!!!!!!!! 목요일 오전에 후다닥 해보자!
변경 사항
DB가 아닌 redis로 저장을 하려고 시도 했습니다.
redis는 새로운 값이 들어오면 이미 저장된 한 개의 값에 덮어 씌워지고 여러 값을 저장하지 못합니다.
key-value 데이터를 사용하는 redis는 값을 한 개만 저장해서 최근 3회 사용한 비밀번호를 기억하고 사용에 제한을 두는 기능을 구현할 수 없어서 redis로 작업하는 부분을 DB로 변경하였습니다.
Todo List
UserService와 UserRepository에서 findTop4ByUserOrderByCreatedAtDesc 쿼리문을 사용하고 있습니다.
RunTime과 포스트맨에서 작동되는 것을 확인했지만 이후 리팩토칭에서 빌더 패턴 사용, 코드의 간결화 등의 문제로 수정되는 것들에 영향을 받아 변경될 것 같습니다.
Check List
포스트맨으로 체크해 보았나요?
테스트 코드를 작성하셨나요?
트러블 슈팅
###UnsatisfiedDependencyException의 해결
id import 경로 문제
import jakarta.persistence.Id; → import org.springframework.data.annotation.Id;
DB로 변경 전, redish 사용시에 일어났던 이슈입니다. redis를 사용하기 위해 만들었던 entity class에 있는 @id의 경로에 오류가 있었습니다.
이슈 번호 : Reseolves: [feature] 비밀번호 수정 #25
@RedisHash(value = "password")
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Password {
@Id
private Long memberId; // 사용자 식별
private String updatedPassword; // 변경된 비밀번호
}public Password toEntity(User user, String encodedPassword) {
Password updatePassword = Password.builder()
.memberId(user.getId())
.updatedPassword(encodedPassword)
.build();
return updatePassword;public interface PasswordRepository extends CrudRepository<Password,Long> {
List<Password> findByMemberId(Long id);
}@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class UpdatePasswordRequestDto {
private String updatePassword;
public void updatePassword(String updatePassword) {
this.updatePassword = updatePassword;
}public UpdatePasswordRequestDto updatePassword(User user, String updatePassword) {
return UpdatePasswordRequestDto.builder()
.id(user.getId())
.updatePassword(updatePassword)
.build();requestDto.updatePassword(passwordEncoder.encode(requestDto.getUpdatePassword()));requestDto.updatePassword(passwordEncoder.encode(requestDto.getUpdatePassword()), user);requestDto.updatePassword(passwordEncoder.encode(requestDto.getUpdatePassword()));
// 회원가입 시 썼던 비밀번호를 PasswordManager 엔티티에 저장
PasswordManager passwordManager = new PasswordManager(user, password); // originalPassword는 회원가입 시 사용된 비밀번호
passwordManagerRepository.save(passwordManager);// 회원가입 시 썼던 비밀번호를 PasswordManager 엔티티에 저장
PasswordManager passwordManager = PasswordManager.builder()
.user(user)
.password(password) // 업데이트된 비밀번호
.build();
passwordManagerRepository.save(passwordManager);@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private final List<PasswordManager> passwordManagerList = new ArrayList<>();UserService
@Transactional
public void updatePassword (UpdatePasswordRequestDto requestDto, User user) {
// 프로필 수정 시 비밀번호를 한 번 더 입력받는 과정
if (!passwordEncoder.matches(requestDto.getPassword(), user.getPassword())) {
throw new IllegalArgumentException("비밀번호 인증에 실패했습니다.");
}
// 최근 3번 사용한 비밀번호 조회
List<PasswordManager> passwordList = passwordManagerRepository.findTop4ByUserOrderByCreatedAtDesc(user);
for (PasswordManager passwordHistory : passwordList) {
if (passwordEncoder.matches(requestDto.getUpdatePassword(), passwordHistory.getPassword())) {
throw new IllegalArgumentException("최근 3번 사용한 비밀번호는 사용할 수 없습니다.");
}
}
// 받아온 비밀번호 암호화 시켜줌
requestDto.updatePassword(passwordEncoder.encode(requestDto.getUpdatePassword()));
User targetUser = userRepository.findById(user.getId())
.orElseThrow(() -> new IllegalArgumentException("회원이 존재하지 않습니다"));
targetUser.updatePassword(requestDto); // 패스워드 업데이트 메소드 호출
passwordManagerRepository.save(new PasswordManager(user, requestDto.getUpdatePassword()));
}
@Transactional
public void updatePassword (UpdatePasswordRequestDto requestDto, User user) {
User loginedUser = findUser(user.getUsername());
//입력한 비밀번호와 DB 내 비밀번호가 동일할 시
if (passwordEncoder.matches(requestDto.getPassword(), loginedUser.getPassword())) {
//가장 최근 사용한 3개의 비밀번호를 가져오기
List<String> usedPasswords = passwordManagerRepository.findPasswordTopThree(loginedUser);
System.out.println("로그:"+usedPasswords);
for(String usedPassword : usedPasswords){
//바꾸려는 비밀번호가 패스워드관리테이블 내 최근 3개의 비밀번호 중 하나와 일치하는 경우
if(passwordEncoder.matches(requestDto.getUpdatePassword(),usedPassword)){
System.out.println("최근 3회 이내 사용된 비밀번호와 같아서 비밀번호를 변경 할 수 없습니다.");
throw new IllegalArgumentException("최근 3회 이내 사용된 비밀번호로는 변경 할 수 없습니다.");
}
System.out.println("최근 3회 이내 사용된 비밀번호와 달라서 비밀번호를 변경 했습니다.");
}
//비밀번호 변경
String newPassword = passwordEncoder.encode(requestDto.getUpdatePassword());
loginedUser.updatePassword(newPassword);
//비밀번호 관리테이블에 추가
PasswordManager passwordManager = new PasswordManager(newPassword, user);
passwordManagerRepository.save(passwordManager);
}
else
throw new IllegalArgumentException("입력한 현재 비밀번호가 일치하지 않습니다.");
}
//user가 db내 존재하는지 검사
private User findUser(String username) {
return userRepository.findByUsername(username).orElseThrow(() ->
new IllegalArgumentException("존재하지 않는 사용자 입니다.")
);
}UserRepository
```java
public interface PasswordManagerRepository extends JpaRepository <PasswordManager,Long> {
List<PasswordManager> findTop4ByUserOrderByCreatedAtDesc(User user);
}
```
- 변경 후
```java
public interface PasswordManagerRepository extends JpaRepository <PasswordManager,Long> {
Optional<PasswordManager> findByUser(User user);
//DESC : 내림차순(높은숫자부터 낮은숫자)
//LIMIT 3 : 3건만 조회
@Query("SELECT pm.password FROM PasswordManager pm WHERE pm.user = :user ORDER BY pm.id DESC LIMIT 3")
List<String> findPasswordTopThree(@Param("user") User user);
}
```
변경 사항
password_manager
회원가입시 입력한 비밀번호를 password_manager에 담아서 저장하고 비밀번호 변경시 기억하여 최근 3회 비밀번호 사용 제한에 포함했습니다.
빌더 패턴으로 변경
UpdatePasswordRequestDto에 빌더 패턴을 써주었으나 UserService 내에 public void signUp(SignupRequestDto requestDto)에서 빌더 패턴을 사용하게 했습니다.
현재 비밀번호 검증 후 비밀번호 변경 가능으로 수정
UpdatePasswordRequestDto에 현재의 비밀번호(password)와 변경할 비밀번호(updatePassword)를 필드로 선언해주고 UserService 내에 public void updatePassword (UpdatePasswordRequestDto requestDto, User user)에 if문을 넣어주었습니다.
Todo List
Check List
포스트맨으로 체크해 보았나요?
테스트 코드를 작성하셨나요?
트러블 슈팅
// 회원가입 시 썼던 비밀번호를 PasswordManager 엔티티에 저장
``PasswordManager passwordManager = PasswordManager.builder()
.user(user)
.password(password) // 업데이트된 비밀번호
.build();
passwordManagerRepository.save(passwordManager);
DB 데이터 삽입으로 테스트
위 코드로 인해 회원가입시 입력했던 비밀번호가 password_manager 테이블에 저장에 되어야 하는데 값이 들어오지 않았습니다.
코드에 문제가 있는 것은 아닌 것 같고... DB에 데이터를 넣어주는 것만으로는 테스트가 이루어질 것 같지 않았습니다.
해결
UserService의 public void signUp(SignupRequestDto requestDto)에서 email, sms 인증 관련을 주석처리하고 포스트맨으로 회원가입을 진행해서 회원가입시 입력했던 비밀번호가 password_manager 테이블에 저장되는 것을 확인했습니다.
또한 최근 3번 사용한 비밀번호 조회를 위해 이전에 작성했던 findTop4ByUserOrderByCreatedAtDesc 관련 로직을 UserService와 PasswordManagerRepository에서 수정해줬습니다. (참고:https://github.com/lazygyu97/SNS)
이슈 번호 : Reseolves: [feature] 비밀번호 수정 #25
해야 할 것 들 …
1. CI/CD
1. CI : ㅇㅇ님의 테스트 코드를 활용해 ci 흐름을 배워보고 적용
2. CD : 백엔드 서버를 배포하여 CD 구현시작
2. 게시글 관련
1. 이미지 업로드 : 게시글 작성, 수정에 대해 이미지 추가 (최대 3장까지 …)
2. 게시글 검색 : 페이징 처리 기반으로 제목, 제목+내용, 내용에 대해 키워드를 통해 검색하기
3. 게시글 추천 : 좋아요 , 지역별로 사용자에게 추천해주는 메서드 (Entity 자체에 지역 칼럼 추가 고려해야함
4. 현재 게시글 조회 시 좋아요와 댓글이 같이 넘어오지 않기 때문에 양방향 설정 해줘야함
3. 서울시 api
1. .각 카테고리 조회 구현 및 페이징 처리 적용
2. 스케줄링에서 RestTemplateConfig를 추가하여 사용하고 있는데 수행하고 있는 작업을 OpenApiService에서 처리하는 방법 생각해보기 -> 강제 업데이를 위해 서비스 단에서 처리해야 좋을 것 같다.
3. 가져온 서비스 정보를 스크랩,댓글,좋아요 하는 방식에 대해 고민해보기 (주기적으로 업데이트 되면서 id값이 바뀌는 문제)
4. 사용자 별 추천 서비스 범위 설정하여 로직 구현
4. 소셜로그인 : 카카오,네이버, 구글 oauth2를 활용한 소셜 로그인 구현
5. 문서 최신화: SA 각 분류 별 변경점에 대한 문서 최신화 , 깃허브 리드 미 작성
6. 알림 기능 : 사용자의 글에 댓글이 달리거나 좋아요가 눌렸을 때 알림
7. 채팅 기능 : 팔로우한 사용자끼리 채팅하는 기능 구현
1. CI/CD
1-1. CI : ㅇㅇ님의 테스트 코드를 활용해 ci 흐름을 배워보고 적용
1-2. CD : 백엔드 서버를 배포하여 CD 구현시작
2. 게시글 관련
2-1. 이미지 업로드 : 게시글 작성, 수정에 대해 이미지 추가 (최대 3장까지 ...)
2-2. 게시글 검색 : 페이징 처리 기반으로 제목, 제목+내용, 내용에 대해 키워드를 통해 검색하기
2-3. 게시글 추천 : 좋아요 , 지역별로 사용자에게 추천해주는 메서드 (Entity 자체에 지역 칼럼 추가 고려해야함
2-4. 현재 게시글 조회 시 좋아요와 댓글이 같이 넘어오지 않기 때문에 양방향 설정 해줘야함
3. 서울시 api
3-1. .각 카테고리 조회 구현 및 페이징 처리 적용
3-2. 스케줄링에서 RestTemplateConfig를 추가하여 사용하고 있는데 수행하고 있는 작업을 OpenApiService에서 처리하는 방법 생각해보기 -> 강제 업데이를 위해 서비스 단에서 처리해야 좋을 것 같다.
3-3. 가져온 서비스 정보를 스크랩,댓글,좋아요 하는 방식에 대해 고민해보기 (주기적으로 업데이트 되면서 id값이 바뀌는 문제)
3-4. 사용자 별 추천 서비스 범위 설정하여 로직 구현
4. 소셜로그인 : 카카오,네이버, 구글 oauth2를 활용한 소셜 로그인 구현
4-5. 문서 최신화: SA 각 분류 별 변경점에 대한 문서 최신화 , 깃허브 리드 미 작성
4-6. 알림 기능 : 사용자의 글에 댓글이 달리거나 좋아요가 눌렸을 때 알림
4-7. 채팅 기능 : 팔로우한 사용자끼리 채팅하는 기능 구현
8. 비밀 번호 수정 리팩토링
8-1. 회원가입할때 회원가입한 비밀번호를 패스워드 매니저에 넣어야함
8-2. 빌드 패턴써서 코드정리
8-3. 현재 비밀번호 입력 받아서 검증한 후에 비밀번호 변경하기
ORM은 "Object-Relational Mapping"의 약어로, 객체와 관계형 데이터베이스 간의 데이터를 변환하고 상호 작용하기 위한 기술입니다. 관계형 데이터베이스는 테이블과 열의 형태로 데이터를 저장하고 관리하는 반면, 객체 지향 프로그래밍에서는 클래스와 객체의 형태로 데이터를 다룹니다. ORM은 이러한 두 가지 모델 간의 간극을 줄이기 위해 사용되며, 개발자들이 객체 지향 프로그래밍 언어를 사용하여 데이터베이스와 상호 작용할 수 있도록 도와줍니다.
ORM의 주요 목표는 다음과 같습니다:
주요한 ORM 프레임워크에는 Hibernate (Java), Entity Framework (.NET), Django ORM (Python), Sequelize (Node.js), SQLAlchemy (Python) 등이 있습니다. 이러한 프레임워크들은 개발자들이 데이터베이스와의 상호 작용을 추상화하고 편리하게 처리할 수 있도록 도와줍니다.
HTTP는 웹에서 데이터를 주고받는 데 사용되는 프로토콜입니다. 그 중에서도 가장 널리 사용되는 메서드는 GET과 POST입니다. 이들 메서드는 데이터를 서버로 보내거나 서버에서 받는 데 사용됩니다.
GET 메서드:
?key=value와 같이 데이터를 붙여서 전달합니다.GET:
https://example.com/profile?user_id=123을 입력합니다.user_id가 123인 사용자의 프로필 정보를 데이터베이스에서 조회합니다.POST 메서드:
POST: