Jwt 를 이용하는 가장 큰 이유 중 하나가 토큰의 자가수용성 이라고 생각한다. 토큰 자체적으로 필요한 정보를 담고 있다는 것인데, 필요한 정보를 담아서 발급해 버리면 해당 토큰으로 요청을 보냈을 때 db 를 찔러볼 필요가 없어진다 (db I/O 의 횟수가 성능에 큰 영향을 미친다).
하지만 누구나 디코딩을 통해 json 에 담긴 정보를 볼 수 있어 토큰엔 민감한 정보를 담지 않는것이 좋다. 따라서 userId 정도를 토큰에 담아 주면, 추후 요청에 함께 온 토큰을 까보고 userId 를 이용해 db에 쿼리를 날려 권한 관련된 정보를 꺼내와 비교하게 된다.
???
그렇다. 이상하다.
우리 팀의 판단으로는 회원인증/인가 구현에 jwt 를 사용하는 이유가 전혀 없었다.
그래서 jwt 대신 Session 을 이용하기로 하였다
참고자료: Why JWTs Suck as Session Tokens
사실 리팩토링 전에 jwt 를 사용하고 있었는데, 아니나 다를까 email 을 집어넣고 그거 까서 db 에 권한 정보를 받아내고 있었다!!
일단 토큰 생성 및 검증하는 클래스를 싹 걷어내면서 권한 인증 부분을 무로 되돌렸다...
이제 세션을 집어넣기 전에 세션에 대한 인튜이션을 주자면,
SessionForOne = Map<key, Information>
Map<SessionId, SessionForOne>
세션은 위와 같은 느낌으로 생성되고 저장된다. SessionId 는 클라이언트에 전달되어 추후 자신의 세션을 찾을때 사용된다.
(https://velog.io/@chlee4858/Java-HttpSession)
로그인 -> 성공시 세션에 사용자 권한 정보 저장 -> 클라이언트에 sessionId (JSESSIONID) 전달 -> 권한이 필요한 요청에 sessionId 를 담아 디바 -> 해당 sessionId 와 매칭되는 세션 정보 불러오기 -> 굳
// UserController.java
@PostMapping("/login")
public ResponseEntity<Void> login(@Validated @RequestBody SignInRequest request) {
userService.signIn(request);
return ResponseEntity.ok().build();
}
// UserService.java
@Transactional(readOnly = true)
public void signIn(SignInRequest request) {
User user = userRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new CustomException("존재하지 않는 회원 입니다."));
if (!encoder.isMatch(request.getPassword(), user.getPassword())) {
throw new CustomException("비밀번호를 다시 확인해주세요");
}
httpSession.setAttribute("user", new SessionUser(user));
}
// AnswerController.java
@PostMapping("/posts/{postId}/answer")
public ResponseEntity<Object> createAnswer(@PathVariable Long postId,
@Validated @RequestBody AnswerRequest answerRequest) {
SessionUser sessionUser = (SessionUser) httpSession.getAttribute("user");
if (!sessionUser.getEnabled()) {
throw new CustomException("이메일 인증이 필요합니다!");
}
if (sessionUser.getUserRole() != UserRole.MANAGER && sessionUser.getUserRole() != UserRole.ADMIN) {
throw new CustomException("답변 권한이 없습니다.");
}
Long answerId = answerService.createAnswer(postId, answerRequest, sessionUser.getId());
return ResponseEntity.created(URI.create("/posts/" + postId + "/answer/" + answerId)).build();
}
// UserController.java
@PostMapping("/logout")
public ResponseEntity<Void> logout(){
httpSession.removeAttribute("user");
return ResponseEntity.ok().build();
}
따봉드립니다