그라찌에 7 -> postman문서화, merge 해보기!!!

류희수·2024년 8월 23일

일단 저번 시간에 클래스들을 정리했으니 마지막으로 테스트를 해보고 post맨 문서화를 진행해보려고 한다!

저번 프로젝트 때에는 스웨거를 썼으나.. 아직 서버가 없으니 일단 포스트맨으로 해보기로 하자!

역시나~ 최종테스트에는 늘 안되는게 있다 이것저것 건들여서 그런가 보다!

  1. 추가정보 조회할때 사진이 안나오는 문제 발견
  2. 추가정보 수정할 때 에러
  3. delete 할때 userAdditional 이랑 fk라 에러발생
  4. 로그인을 할 때 마다 리프레시 토큰이 계속해서 생기는 에러

정도가 있었다~ 차근차근 하나씩 해보도록 하자!


1. 추가정보 조회할때 사진이 안나오는 문제 발견

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으로 직렬화할 필드를 찾는다!

오호... 사용하지 않는다고 다 지워버렸는데 내부적인 로직은 생각하지 못했다! 좋은 걸 알아간다!!


2. 추가정보 수정할 때 에러

no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION disabled); line: 2, column: 5]

라는 오류가 나고 있다.

해석해보면
기본 생성자와 같은 생성자가 존재하지 않는다 : 객체 값을 역직렬화할 수 없다!

바로 기본생성자를 찾으로 가보자

위에서 올린 DTO 코드와똑같이 역시나 빠져 있었다.

Jackson은 역직렬화를 위해 기본 생성자가 필요하다!.
기본 생성자가 없는 클래스에서는 JSON 데이터를 객체로 변환할 수 없다.

저번시간에 정리한 내용인데 지워져있었다...!
간단하게 해결~~


3. delete 할때 userAdditional 이랑 fk라 에러발생

Cannot delete or update a parent row: a foreign key constraint fails (grazie.user_additional_info, CONSTRAINT FKbqevgmgnanqjp5r743417r93s FOREIGN KEY (user_id) REFERENCES user (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;
  1. alter로 테이블 구조를 변경한다.

  2. FKfgk1klcib7i15utalmcqo7krt라는 제약 조건의 이름을 추가한다. (제약 조건에도 이름이 있는 지 이제 알았다.)

  3. user_id열user테이블id열을 참조하여 관계 정의

  4. ON DELETE CASCADE를 사용하여 부모 테이블의 행이 삭제될 때 자식 테이블의 관련 행도 자동으로 삭제 되도록 만듬

해결~~


4. 로그인 마다 리프레시 토큰이 중복되서 저장되는 문제

  • ( 리프레시 토큰의 무효화)

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까지 완료하였음!

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

profile
자바를자바

0개의 댓글