사용하게 된 이유
Redis를 사용하자고 결정하게 된 이유 3가지 문제를 해결하기 위해서입니다.
- 로그인 후 유저가 작성한 데이터의 삽입, 삭제, 변경 등에 대해서 해당 사용자가 맞는지 확인하기 위해서
- 1회 발급을 보장하기 위해서
- 빠른 처리속도를 위해서
현재 만들고 있는 프로젝트의 인증방식은 JWT 방식을 사용하여 회원의 정보를 관리하도록 설계했습니다. 초기 계획한 프로젝트가 끝나가면서 인증 절차가 부실한 점을 생각하였고 이러한 문제점의 이유와 해결하면서 얻게 되는 이점이 무엇이 있는지 생각해보고자 합니다.
1. 로그인 후 유저가 작성한 데이터의 삽입, 삭제, 변경 등에 대해서 해당 사용자가 맞는지 확인하기 위해서
Controller Logic :
// Controller
@PutMapping(value = "/quote/{id}")
public ResponseEntity<?> updateUserQuote(@PathVariable Long id, @RequestBody QuoteDTO quoteDTO,
@UserAuthToken UserTokenDTO tokenDTO) {
if (quoteDTO.getIsPublish() == null && quoteDTO.getQuote() == null
&& quoteDTO.getAuthor() == null) {
return new ResponseEntity<>(ResponseStatus.FAILURE, HttpStatus.OK);
}
userQuoteService.updateQuote(id, quoteDTO, tokenDTO);
return new ResponseEntity<>(ResponseStatus.SUCCESS, HttpStatus.OK);
}
Service Logic :
// Service
@Transactional(rollbackFor = Exception.class)
public QuoteEntity updateQuote(Long id, QuoteDTO quoteDTO, UserTokenDTO tokenDTO) {
QuoteEntity quoteEntity = userQuoteRepository.findById(id).get();
Publish publish = quoteEntity.getIsPublish();
String quote = quoteDTO.getQuote();
String author = quoteDTO.getAuthor();
if (quoteDTO.getIsPublish().equals("private")) {
quoteEntity.setIsPublish(publish.PRIVATE);
} else {
quoteEntity.setIsPublish(publish.PUBLISH);
}
if (quote != null) {
quoteEntity.setQuote(quoteDTO.getQuote());
}
if (author != null) {
quoteEntity.setAuthor(quoteDTO.getAuthor());
}
return userQuoteRepository.save(quoteEntity);
}
Controller 로직에서 유저로부터 얻은 토큰을 해석하여 UserTokenDTO에 담아 Service 로직에서 사용할 수 있도록 설계했습니다.
repository에서 해당 ID의 유저의 정보를 호출하여 객체를 저장하는 로직에서 Toekn정보에서 얻은 유저의 ID를 사용하기 때문에 해당 유저가 수정하는 데이터가 자신의 데이터가 맞는지 확인할 수 없는 방법은 없습니다.
물론 프런트 쪽에서 그러한 로직을 추가하거나 DB에서 꺼낸 QuoteEntity가 현재 Token에 들어있는 유저 ID와 같은지 확인하는 방법이 있지만 모든 Service에 추가해야 하고 새롭게 만들게 될 Service 로직에도 적용해야 하기 때문에 설계가 좀 더 복잡해지고 관리하기 어렵다고 판단하였습니다.
2. 1회 발급을 보장하기 위해서

서버로부터 발급된 토큰은 서버가 관리하는 것이 아닌 클라이언트 쪽에서 가지고 있어야 하기 때문에 서버에 데이터를 요청하기 위해서는 어딘가 저장을 해둬야 합니다. 그렇기에 저는 Application Storage에 토큰을 저장하도록 설계했지만 이러한 설계에는 몇 가지의 문제점을 가지고 있었습니다.
- 발급한 토큰이 유효기간을 갖고 있지 않다면 무한히 서버에 접근하여 사용할 수 있다는 것입니다.
- 또한 크롬 및 기타 익스플로러마다 Storage에 저장되기 때문에 로그인이 요청될 경우 수많은 토큰들이 발생한다는 것입니다.

물론 유효기간을 설정하면 1번의 문제는 해결될 것입니다. 하지만 2번의 문제는 사용자가 A위치에서 B위치로 옮겨 로그인하더라도 A에 위치한 토큰은 만료될 때까지 서버의 연결은 보장받기 때문에 A에 다른 사용자가 있을 경우 보안에 대한 문제를 얻게 될 것입니다.

만일 Redis에 발행된 토큰이 기록된다면 TokenA를 사용한 B의 서버 접근을 차단할 수 있는 효과를 얻을 것이며, 하나의 토큰이 발행되는 것을 보장할 수 있습니다.
3. 빠른 처리속도를 위해서
Redis는 일반 DB와 달리 Memory에 저장되기 때문에 Read / Write 속도가 빠릅니다. 만일 DB에 사용자의 토큰을 저장하여 접근 권한을 확인하게 된다면 하드 디스크로부터 데이터를 Read / Write 하기 때문에 상대적으로 Memory에서 Read / Write를 하는 것보다 느릴 수 있습니다.
또한 Redis는 Sort, Set, Hash를 비롯하여 다양한 자료구조를 제공하기 때문에 2번의 문제를 해결하는데 별도의 알고리즘을 만들 필요 없기 때문에 쉽게 해결할 수 있을 것입니다.
단점이 있음에도 사용하게 된 이유
Redis의 장점과 JWT의 장점들을 활용하여 이러한 문제의 해결에 도움을 줄 수 있지만, 단점도 분명히 존재합니다.
JWT를 사용함으로써 얻는 이익은 서버의 부담을 줄일 수 있는 것입니다. JWT의 Payload안에는 인증에 필요한 정보를 담고 있기 때문에 서버는 따로 인증에 대한 부담을 줄일 수 있지만, 너무 많은 정보를 담게 된다면 부하를 줄 수 있다는 것입니다. 또한 인증을 위해 Payload에 중요한 데이터를 넣게 된다면 사용자의 데이터가 일부 노출되는 상황이 발생할 수도 있습니다.
Redis 또한 마찬가지로 Memory에 저장하는 방식이기 Read / Write는 빠르지만 DB처럼 많은 양의 데이터를 저장할 수 없다는 한계를 가지고 있습니다. JWT가 갖고 있는 장점 중 하나인 인증을 위한 별도의 저장소를 관리할 필요가 없다는 장점이 사라지게 됩니다.
그럼에도 사용해야만 하는 이유는 프론트와 백엔드를 분리하였기 때문입니다. 현재 GitHub Page에서 배포중인 프런트는 정적 리소스를 가지고 데이터를 보여주고 있기 때문에 백엔드에서의 인증과 권한이 필수적입니다. 또한 단점보다 장점이 더욱 많기 때문에 설계해야만 했던 이유이기도 합니다.
앞으로 추가할 것
Spring Security를 사용하여 인증과 권한을 좀 더 쉽에 제어할 수 있지 않을까 생각되며, Redis Server를 새로 만들어 BackEnd Server와 Redis Server를 분리할 것 같습니다. 현재로서는 돈이 없기 때문에 BackEnd Server와 Redis Server 그리고 Jenkins Server, DB Server가 한 곳에 전부 모여있지만 현재 프로젝트는 MSA를 목표로 만들고 있는 만큼 좀 더 고민을 해봐야 할 것 같습니다.