일단 저번 시간에 클래스들을 정리했으니 마지막으로 테스트를 해보고 post맨 문서화를 진행해보려고 한다!
저번 프로젝트 때에는 스웨거를 썼으나.. 아직 서버가 없으니 일단 포스트맨으로 해보기로 하자!
역시나~ 최종테스트에는 늘 안되는게 있다 이것저것 건들여서 그런가 보다!
- 추가정보 조회할때 사진이 안나오는 문제 발견
- 추가정보 수정할 때 에러
- delete 할때 userAdditional 이랑 fk라 에러발생
- 로그인을 할 때 마다 리프레시 토큰이 계속해서 생기는 에러
정도가 있었다~ 차근차근 하나씩 해보도록 하자!
public class UserAdditionalInfoDTO {
public UserAdditionalInfoDTO(String nickname, String profileImage, Gender gender) {
this.nickname = nickname;
this.profileImage = profileImage;
this.gender = gender;
}
private final String nickname;
private final String profileImage;
private final Gender gender;
public Gender getGender() {
return gender;
}
public String getNickname() {
return nickname;
}
}
분명히 getter는 사용되지 않고 있어서 제거했는데 혹시 몰라서
추가하고 돌렸더니 정상적으로 출력됬다,,,;;; 왜??
JSON 직렬화 라이브러리(예: Jackson)는 객체를 JSON으로 변환할 때 일반적으로 필드의 getter 메서드를 사용하여 값을 가져온다!
즉 Jackson 라이브러리는 클래스의 getter 메서드를 통해 JSON으로 직렬화할 필드를 찾는다!
오호... 사용하지 않는다고 다 지워버렸는데 내부적인 로직은 생각하지 못했다! 좋은 걸 알아간다!!
no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATIONdisabled); line: 2, column: 5]
라는 오류가 나고 있다.
해석해보면
기본 생성자와 같은 생성자가 존재하지 않는다 : 객체 값을 역직렬화할 수 없다!
바로 기본생성자를 찾으로 가보자
위에서 올린 DTO 코드와똑같이 역시나 빠져 있었다.
Jackson은 역직렬화를 위해 기본 생성자가 필요하다!.
기본 생성자가 없는 클래스에서는 JSON 데이터를 객체로 변환할 수 없다.
저번시간에 정리한 내용인데 지워져있었다...!
간단하게 해결~~
Cannot delete or update a parent row: a foreign key constraint fails (
grazie.user_additional_info, CONSTRAINTFKbqevgmgnanqjp5r743417r93sFOREIGN KEY (user_id) REFERENCESuser(id))
대충 해석해보면 user 테이블의 id 열을 참조하는 user_additional_info 테이블의 외래 키 제약 조건이 있어서,
부모 테이블(user)에서 삭제하거나 업데이트할 때 제약 조건이 위배된다는 의미.
userAdditionalInfo 를 해결했더니 refreshToken 도 해결하라고 한다.
내가 해결한 sql문은 다음과 같았다.
ALTER TABLE refresh_token
ADD CONSTRAINT FKfgk1klcib7i15utalmcqo7krt
FOREIGN KEY (user_id) REFERENCES user(id)
ON DELETE CASCADE;
alter로 테이블 구조를 변경한다.
FKfgk1klcib7i15utalmcqo7krt라는 제약 조건의 이름을 추가한다. (제약 조건에도 이름이 있는 지 이제 알았다.)
user_id열이 user테이블의 id열을 참조하여 관계 정의
ON DELETE CASCADE를 사용하여 부모 테이블의 행이 삭제될 때 자식 테이블의 관련 행도 자동으로 삭제 되도록 만듬
해결~~

save로직만 했더니 리프레시 토큰이 로그인마다 계속해서 새롭게 발급되고 있었다!
리프레시 토큰 서비스에 여러가지 리프레시 토큰 인증 관련 서비스들을 추가해보자!
public LoginResponseDTO login(String userid, String password) {
User user = userRepository.findByUserId(userid)
.orElseThrow(() -> new RuntimeException("유효하지 않은 아이디입니다. "));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new RuntimeException("비밀번호가 일치 하지 않습니다.");
}
String accessToken = jwtUtil.generateAccessToken(user.getId());
RefreshToken existRefreshToken = refreshTokenService.checkRefreshToken(user);
if (existRefreshToken != null) {
if (existRefreshToken.isRevoked() || existRefreshToken.getExpiresAt().isBefore(LocalDateTime.now())) {
System.out.println("기존에 리프레시 토큰이 무효화 되어 재 발급 합니다잉.");
refreshTokenRepository.delete(existRefreshToken);
String newRefreshToken = jwtUtil.generateRefreshToken(user.getId());
refreshTokenService.saveRefreshToken(user);
return new LoginResponseDTO(accessToken, newRefreshToken);
}
System.out.println("기존 리프레시 토큰이 유효하므로 재발급 x ");
return new LoginResponseDTO(accessToken, existRefreshToken.getToken());
}
// 아예 없는 경우
String newRefreshToken = jwtUtil.generateRefreshToken(user.getId());
refreshTokenService.saveRefreshToken(user);
return new LoginResponseDTO(accessToken, newRefreshToken);
}
public void logOut(String refreshToken) {
try {
System.out.println("로그아웃 요청에서 받은 리프레시 토큰: " + refreshToken);
Optional<RefreshToken> token = refreshTokenRepository.findByToken(refreshToken);
if (token.isPresent()) {
RefreshToken existToken = token.get();
if (existToken.isRevoked()) {
throw new RuntimeException("리프레시 토큰은 이미 무효화되었습니다.");
}
existToken.setRevoked(true);
refreshTokenRepository.save(existToken);
} else {
System.err.println("리프레시 토큰을 찾을 수 없습니다: " + refreshToken);
throw new RuntimeException("리프레시 토큰을 찾을 수 없습니다!");
}
} catch (RuntimeException e) {
System.err.println("에러 발생: " + e.getMessage());
throw e;
}
수정된 로그인 & 로그아웃 로직
refreshToken을 로그아웃 할때 revoked = true 로 바꾸어주고
로그인 시 revoked가 true면 삭제하고 재발급 받는 로직, 만료됬을 때 로직등을 추가 하였음!
로그인 로직에 refreshToken 관련 로직이 덕지더직 있는 것 같아서,,,
sout찍은 것 좀 정리하고 refreshToken에 관련된 것들은RefreshService에 따로 메소드를 만들어 옮겨 주었다!
요런식으로!
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final RefreshTokenService refreshTokenService;
private final RefreshTokenRepository refreshTokenRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public LoginResponseDTO login(String userid, String password) {
User user = userRepository.findByUserId(userid)
.orElseThrow(() -> new RuntimeException("유효하지 않은 아이디입니다. "));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new RuntimeException("비밀번호가 일치 하지 않습니다.");
}
String accessToken = jwtUtil.generateAccessToken(user.getId());
String refreshToken = refreshTokenService.validateAndRenewRefreshToken(user);
return new LoginResponseDTO(accessToken, refreshToken);
}
public void logOut(String refreshToken) {
try {
RefreshToken existToken = refreshTokenService.findRefreshToken(refreshToken);
refreshTokenService.checkRevokedToken(existToken);
refreshTokenService.setRevokeToken(existToken);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
깔끔하게 정리 했다!
근데.. 그냥 로그아웃 할 떄 지우면 된다 아님....?
음.. 그래도 비밀번호 변경 로직 할 때 나중에 조금 더 유연하게 사용해보자 일단 이 방식으로 진행한다!
또한 sql -> redis라는 휘발성 sql로 변경해야 하고 refreshToken 암호화도 진행해야 하니 추후에 리펙토링 때 더 자세히 다루기로 하겠다!
https://velog.io/@tco0427/Refresh-Token
추후에 리펙토링 할떄 참고할 자료

포스트맨 문서화까지 완료한 뒤에
메인브렌치에 merge까지 완료하였음!

이젠 쿠폰을 개발해볼 생각이다!