[SpringBoot] GDSC project Error 해결기 - 쿠키 , QueryDsl , 영속성

유형찬·2022년 9월 22일
0

GDSC_BLOG_PROJECT

목록 보기
3/5

GDSC-WEB-BACKEND Error 해결기

1. Cookies Expired Error

위와 같은 에러가 발생했다. Refresh 로직 중에 발생한 에러이다.

위는 백엔드에서 전해준 Cookies : Access Token에 접근 하려다가 생긴 에러이다.

무슨 말이냐, 백엔드에서 전해준 쿠키에 프론트 엔드에서 접근하려고 했는데, 쿠키가 만료되었다는 말이다.

백엔드에서 Access Token 만료기간을 30분으로 줬어서 쿠키도 만료기간을 30분으로 줬다.

그러나 여기서 문제가 생겼다.

쿠키의 만료기간 expires / Max-age 가 만료되면 브라우저에서 어떤 동작을 하는지 이번 문제를 해결 하며 알았다.

예를 들어서 쿠키의 만료기간이 30분이고, 30분이 지나면 브라우저는 쿠키를 삭제했다.

브라우저가 재접속을 감지하면 저장된 토큰 중 expires 가 된 쿠키를 삭제 한다.

때문에 Access Token을 Refresh 하려 백엔드에 요청을 하려 했는데 access Token 이라는 이름의
토큰이 없는데 접근 하려고 해서
에러가 발생 했다.

해결 방법

  1. 쿠키의 만료기간을 늘린다.

일단 만료 기간을 늘리는 방식으로 해결 했다.

왜냐면 토큰 max-age 때문에 토큰이 사라졌기 때문에 쿠기의 만료기간이 문제였던 것이다.

따라서 쿠키의 만료기간을 늘려서 토큰이 사라지기 전에 프론트에서 접근 할 수 있도록 했다.

그런데 어떻게 프론트엔드는 Access token의 만료기간을 알 수 있을까?

이전에는 Cookies 의 max-age를 기준으로 정했었다. 그러나 이는 쿠키의 만료기간이기 때문에

토큰의 만료기간과는 다르게 설정 해주어야 했다.

그래서 토큰의 만료기간을 백엔드에서 프론트엔드로 전달 해주는 방식으로 해결 했다.

        CookieUtil.addCookie(request,response, Authorization, newAccessToken.getToken(), cookieExpiry);
        CookieUtil.deleteCookie(request, response, "expires_in");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        CookieUtil.addCookie(request, response,
                "expires_in" ,
                sdf.format(new Date(now.getTime() + refreshTokenExpiry)),
                cookieExpiry);
        Map<String,String>  tokenMap = new HashMap<>();

위 코드는 백엔드에서 쿠키를 추가하는 코드이다.

expires_in 이라는 쿠키를 추가 했는데 이는 토큰의 만료기간을 담고 있다.

또한 ISO 8601 를 맞추기 위해서 Date Format을 설정 해주었다. 그냥 브라우저에서 토큰 값을 열어보면 보이는 날짜 형식이다.

아래는 예상 프론트 refresh 로직이다. 프론트 개발자가 아니라 간단한 예시이다.

    const refreshToken = () => {
        const expires_in = getCookie("expires_in");
        const now = new Date();
        if (expires_in < now) {
            // refresh 
        }
    }
    const expires_in = getCookie("expires_in");
    const expires_in_date = new Date(expires_in);
    const now = new Date();
    if (expires_in_date < now) {
        console.log("토큰 만료");
        refreshToken();
    }

또 , refresh Token의 만료기간으로 백엔드에서 전해주는 쿠키의 만료기간을 설정 해주었다.

이는 refresh Token이 validate 할 때 까지 무한정 로그인을 유지 할 수 있도록 하기 위해서이다.

이 처럼 오늘 발생한 문제를 해결 하면서 새로운 것을 배웠다. 개발이란 복잡한 것...

QueryDsL 영속성 Context 문제

오늘은 QueryDsl을 사용하면서 발생한 문제이다.

최근에 프로젝트를 진행하면서 QueryDsl을 사용하게 되었다.

이전 포스팅을 통해 Query DSL 을 썻었는데 여기서 간과했던 문제가 있었다.

 public Member findByUserIdWithSlack(String id) {
        // querydsl 사용
        JPAQueryFactory query = new JPAQueryFactory(em);

        QMember m = new QMember("m");
        QSlackMemberInfo s = new QSlackMemberInfo("s");
        Tuple t = query.select(m, s)
                .from(m)
                .leftJoin(s)
                .on(m.eq(s.userId)).where(m.userId.eq(id)).fetchOne();

        if (Objects.isNull(t)) {
            throw new IllegalArgumentException("해당 유저가 존재하지 않습니다.");
        }
        Member returnMember =
                Member.builder()
                .userId(t.get(m).getUserId())
                .email(t.get(m).getEmail())
                .memberInfo(t.get(m).getMemberInfo())
                .emailVerifiedYn(t.get(m).getEmailVerifiedYn())
                .modifiedAt(t.get(m).getModifiedAt())
                .profileImageUrl(t.get(m).getProfileImageUrl())
                .providerType(t.get(m).getProviderType())
                .role(t.get(m).getRole())
                .uploadDate(t.get(m).getUploadDate())
                .username(t.get(m).getUsername())
                .build();
        if(t.get(s) != null) {
            returnMember.setProfileImageUrl(Objects.requireNonNull(t.get(s)).getProfileImage512());
        }
        return returnMember;

    }

위 코드는 QueryDsl을 사용하여 Member와 SlackMemberInfo를 조인하여 가져오는 코드이다.

이 코드는 데이터를 불러오는 것에서는 정상적으로 작동을 하였다.

그런데 이 코드를 테스트 코드로 작성하려고 하니 문제가 발생하였다.

당연하게도 위 코드는 영속성 컨텍스트에 등록된 객체를 return 하지 않는다.

멍청하게 생각했던 것 같은데 새로운 Object를 deep Copy 를 해놓고는 왜 영속성 컨텍스트에 등록이 되지 않는지 이해를 하지 못했다.

영속성 컨텍스트에 등록된 객체를 return 하려면 아래와 같이 작성해야 한다.

    public Member findByUserIdWithSlack(String id) {
        // querydsl 사용
        JPAQueryFactory query = new JPAQueryFactory(em);

        QMember m = new QMember("m");
        QSlackMemberInfo s = new QSlackMemberInfo("s");
        Tuple t = query.select(m, s)
                .from(m)
                .leftJoin(s)
                .on(m.eq(s.userId)).where(m.userId.eq(id)).fetchOne();

        if (Objects.isNull(t)) {
            throw new IllegalArgumentException("해당 유저가 존재하지 않습니다.");
        }
        Member returnMember =
                Member.builder()
                        .userId(t.get(m).getUserId())
                        .email(t.get(m).getEmail())
                        .memberInfo(t.get(m).getMemberInfo())
                        .emailVerifiedYn(t.get(m).getEmailVerifiedYn())
                        .modifiedAt(t.get(m).getModifiedAt())
                        .profileImageUrl(t.get(m).getProfileImageUrl())
                        .providerType(t.get(m).getProviderType())
                        .role(t.get(m).getRole())
                        .uploadDate(t.get(m).getUploadDate())
                        .username(t.get(m).getUsername())
                        .build();
        if(t.get(s) != null) {
            returnMember.setProfileImageUrl(Objects.requireNonNull(t.get(s)).getProfileImage512());
        }
        return em.merge(returnMember);

    }

아래 코드를 테스트 해보다 알게 되었다. 그런데 이 것은 내가 원하는 결과가 아니였다.

Slack Info 가 있는 경우 Profile Image를 SlackInfo에 저장된 Image 를 가져오고 싶었는데
위와 같이 할 경우 SlackInfo 가 있는 경우에 구글 이미지 정보가 덮어씌워 진다.
이것은 내가 원한 결과가 아니였다. 따라서 SlackInfo 가 없는 Native Entity query Function을 만들어서 함수를 바꿔 줬다.

그리고 아래 코드를 작성 할 때 왜 dirty Checking 이 안되는지 몇 시간동안이나 고민한 나였다 ㅠ.ㅠ


@Transactional
    public void 맴버권한수정(String userId, RoleType role){
        // 영속성 컨텍스트 내부에 있는 객체를 가져옴

        Member member = repository.findByUserIdWithSlack(userId);
        log.info("member : {}", member);
        // Validations
        validate(member);

        member.setRole(role);
        member.setEmail("change@test.com");
        // password null error https://java8.tistory.com/509

    }
profile
rocoli에요

0개의 댓글