Redis Repository오류

wish17·2023년 5월 17일
0

오류정리

목록 보기
6/7

문제점

리프래쉬 토큰을 redis에서 관리하려고 구현하는 과정에서 발생한 문제다.

redis에서 value(rtk)값으로 해당하는 데이터를 가져오는게 안된다.
분명히 아래와 같이 redis에 저장되어 있고 디버깅해보면 저장된 value값 그대로 대입되는걸 여러번 확인했는데 오류가 반복된다.

127.0.0.1:6379> HGETALL refreshToken:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0MTFAZ21haWwuY29tIiwiaWF0IjoxNjg0MzQ5MjAxLCJleHAiOjE2ODQzNzQ0MDF9.R_kJJ_Wqh36nYahBCP0DNCh2meQcJeupSuP8WgIuF0A
1) "refreshToken"
2) "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0MTFAZ21haWwuY29tIiwiaWF0IjoxNjg0MzQ5MjAxLCJleHAiOjE2ODQzNzQ0MDF9.R_kJJ_Wqh36nYahBCP0DNCh2meQcJeupSuP8WgIuF0A"
3) "_class"
4) "com.codestates.edusync.security.auth.refresh.RefreshToken"
5) "memberId"
6) "6"
127.0.0.1:6379>

코드

리프래쉬 토큰을 이용해 엑세스 토큰을 재발급 하려고 하면
refreshTokenRepository.findByRtk(refreshToken);메서드를 통해 redis에서 올바른 객체를 가져오지를 못하고 계속 null을 가져와서 Invalid refresh token [redis]가 출력된다.

@RestController
@RequestMapping("/refresh")
@RequiredArgsConstructor
public class RefreshController {
    private final JwtTokenizer jwtTokenizer;
    private final MemberRepository memberRepository;
    private final TokenService tokenService;
    private final RefreshTokenRepository refreshTokenRepository;

    @PostMapping
    public ResponseEntity<String> refreshAccessToken(HttpServletRequest request) { // 리프레쉬 토큰 받으면 엑세스 토큰 재발급
        String refreshTokenHeader = request.getHeader("Refresh");
        if (refreshTokenHeader != null && refreshTokenHeader.startsWith("Bearer ")) {
            String refreshToken = refreshTokenHeader.substring(7);
            try {
------------------------------------------오류 발생------------------------------------------     
                RefreshToken refreshTokenObj = refreshTokenRepository.findByRtk(refreshToken);
------------------------------------------오류 발생------------------------------------------
                if (refreshTokenObj == null) {
                    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid refresh token [redis]");
                }

                Jws<Claims> claims = jwtTokenizer.getClaims(refreshToken, jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey()));

                String email = claims.getBody().getSubject();
                Optional<Member> optionalMember = memberRepository.findByEmail(email);

                if (optionalMember.isPresent()) {
                    Member member = optionalMember.get();
                    String accessToken = tokenService.delegateAccessToken(member);

                    return ResponseEntity.ok().header("Authorization", "Bearer " + accessToken).body("Access token refreshed");
                } else {
                    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid member email");
                }
            } catch (JwtException e) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid refresh token");
            }
        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Missing refresh token");
        }
    }
}

@Repository
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
    RefreshToken findByRtk(String refreshToken);
}
@RedisHash(value = "refreshToken", timeToLive = 420)
public class RefreshToken {

    @Id
    private String rtk;
    private Long memberId;

    public RefreshToken(final String rtk, final Long memberId) {
        this.rtk = rtk;
        this.memberId = memberId;
    }

    public String getRefreshToken() {
        return rtk;
    }

    public Long getMemberId() {
        return memberId;
    }
}
@Service
@RequiredArgsConstructor
public class TokenService {
    private final RefreshTokenRepository refreshTokenRepository;
    private final JwtTokenizer jwtTokenizer;
    private final MemberUtils memberUtils;

    // Access Token을 생성하는 구체적인 로직
    public String delegateAccessToken(Member member) {
        String email = member.getEmail();

        Map<String, Object> claims = new HashMap<>();
        claims.put("email", email);
        claims.put("roles", member.getRoles());
        claims.put("nickName", member.getNickName());

        Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getAccessTokenExpirationMinutes());

        String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());

        String accessToken = jwtTokenizer.generateAccessToken(claims, email, expiration, base64EncodedSecretKey);

        return "Bearer " + accessToken;
    }

    // Refresh Token을 생성하는 구체적인 로직
    public String delegateRefreshToken(Member member) {
        String subject = member.getEmail();
        Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getRefreshTokenExpirationMinutes());
        String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());

        String refreshToken = jwtTokenizer.generateRefreshToken(subject, expiration, base64EncodedSecretKey);

        /*
        redis 설치가 귀찮으시다면 아래 두줄 주석처리하시면 됩니다.
         */

        RefreshToken rtk = new RefreshToken(refreshToken, member.getId());
        refreshTokenRepository.save(rtk);

        return "Bearer " + refreshToken;
    }
}

해결방법

2가지 문제점이 있었다.

1. CrudRepository를 extends하는데 @repository 애너테이션을 붙혀서 발생하는 문제

아래와 같이 RefreshTokenRepository 인터페이스에 @Repository 애너테이션을 삭제해 해결할 수 있었다.

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
    Optional<RefreshToken> findByRtk(String refreshToken);
}

2. id에 해당하는 필드는 필드값 기준으로 작성하면 안된다.

일반적으로 필드값 기준으로 한다면 기존의 findByRtk()가 맞지만 rtk필드는 @ID 애너테이션 붙어있으므로 findById()로 작성하는게 맞다.

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
    Optional<RefreshToken> findById(String refreshToken);
}

정상작동!!!


tmi

이것저것 테스트하는 과정에서 리프래쉬 토큰의 길이가 너무 길어 아래와 같이 간단하게 바꿔서 테스트 했다.

@RestController
@RequestMapping("/redis")
@RequiredArgsConstructor
public class RedisTest {
    private final RefreshTokenRepository refreshTokenRepository;

    @PostMapping
    public ResponseEntity<String> redisTest(HttpServletRequest request) {

        RefreshToken rtk = new RefreshToken("111", 111L);
        refreshTokenRepository.save(rtk);

        Optional<RefreshToken> refreshTokenObj = refreshTokenRepository.findByRtk("111");

        return ResponseEntity.ok().body(refreshTokenObj.get().getRefreshToken());

    }
}

데이터 저장은 항상 아래와 같이 2가지 방법으로 확인했고 저장까지는 항상 문제 없었다.
따라서 redis에서 데이터를 꺼내오는 작업에서 발생하는 문제라고 범위를 줄일 수 있었고 오류를 해결하는데 걸리는 시간을 줄일 수 있었다.

127.0.0.1:6379> HGETALL refreshToken:111
1) "_class"
2) "com.codestates.edusync.security.auth.refresh.RefreshToken"
3) "memberId"
4) "111"
5) "rtk"
6) "111"
127.0.0.1:6379>

0개의 댓글