Skip to content

Commit

Permalink
[#33] Feat: JWT 토큰 만료 reissue 재발급 기능 완성
Browse files Browse the repository at this point in the history
  • Loading branch information
tkguswls1106 committed May 17, 2024
1 parent 278bc9e commit fbf84fc
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.sajang.devracebackend.controller;

import com.sajang.devracebackend.dto.auth.ReissueRequestDto;
import com.sajang.devracebackend.dto.auth.SignupRequestDto;
import com.sajang.devracebackend.dto.auth.SignupResponseDto;
import com.sajang.devracebackend.dto.auth.TokenDto;
import com.sajang.devracebackend.response.ResponseCode;
import com.sajang.devracebackend.response.ResponseData;
import com.sajang.devracebackend.service.AuthService;
import com.sajang.devracebackend.service.TokenService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
Expand All @@ -24,6 +27,7 @@
public class AuthController {

private final AuthService authService;
private final TokenService tokenService;


@PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Expand All @@ -35,4 +39,11 @@ public ResponseEntity<ResponseData<SignupResponseDto>> signup(
SignupResponseDto signupResponseDto = authService.signup(imageFile, signupRequestDto);
return ResponseData.toResponseEntity(ResponseCode.CREATED_USER, signupResponseDto); // 이 reponseDto 내에 새로운 JWT Access 토큰이 들어있음. 이후 앞으로는 이걸로 헤더에 장착해야함.
}

@PostMapping("/reissue")
@Operation(summary = "JWT Access Token 재발급(로그인 유지) [jwt X]")
public ResponseEntity<ResponseData<TokenDto>> reissue(@RequestBody ReissueRequestDto reissueRequestDto) {
TokenDto tokenDto = tokenService.reissue(reissueRequestDto);
return ResponseData.toResponseEntity(ResponseCode.REISSUE_SUCCESS, tokenDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.sajang.devracebackend.dto.auth;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ReissueRequestDto {

private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public ResponseEntity handleRoomBadRequestException(RoomBadRequestException ex)
return ResponseData.toResponseEntity(ResponseCode.BAD_REQUEST_ROOM);
}

@ExceptionHandler(TokenBadRequestException.class)
public ResponseEntity handleTokenBadRequestException(TokenBadRequestException ex) {
log.error(ex.getErrorStatus() + " " + ex.getErrorMessage() + "\n" + "==> error_messege / " + ex.getMessage());
return ResponseData.toResponseEntity(ResponseCode.BAD_REQUEST_TOKEN);
}

@ExceptionHandler(UserBadRequestException.class)
public ResponseEntity handleUserBadRequestException(UserBadRequestException ex) {
log.error(ex.getErrorStatus() + " " + ex.getErrorMessage() + "\n" + "==> error_messege / " + ex.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public enum ResponseCode {
// Token 실패 응답
TOKEN_EXPIRED(StatusItem.UNAUTHORIZED, MessageItem.TOKEN_EXPIRED),
TOKEN_ERROR(StatusItem.UNAUTHORIZED, MessageItem.TOKEN_ERROR),
BAD_REQUEST_TOKEN(StatusItem.BAD_REQUEST, MessageItem.BAD_REQUEST_TOKEN),

// ===================== //

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sajang.devracebackend.response.exception.exception400;

import com.sajang.devracebackend.response.exception.CustomException400;
import com.sajang.devracebackend.response.responseitem.MessageItem;

public class TokenBadRequestException extends CustomException400 {

public TokenBadRequestException(String message) {
super(MessageItem.BAD_REQUEST_TOKEN, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class MessageItem {

public static final String TOKEN_EXPIRED = "ERROR - JWT 토큰 만료 에러";
public static final String TOKEN_ERROR = "ERROR - 잘못된 JWT 토큰 에러";
public static final String BAD_REQUEST_TOKEN = "ERROR - 잘못된 토큰 요청 에러";

// < Etc >
public static final String READ_DATA = "SUCCESS - 데이터 통합 조회 성공"; // 문제풀이 페이지 내 데이터들 조회 결과 응답시 사용할 예정.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public Authentication getAuthentication(String accessToken) {
Claims claims = parseClaims(accessToken); // Access Token의 Payload에 저장된 Claim을 꺼내옴. (JWT 토큰에서 사용자의 아이디와 권한 정보를 획득할 목적)

if (claims.get(AUTHORITIES_KEY) == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다."); // 500 Error가 적절함.
throw new RuntimeException("권한 정보가 없는 토큰입니다."); // 클라이언트가 잘못된 요청을 한 것이 아니라, 서버에서 처리 중에 예기치 않은 에러가 발생한 것이기에, 403이 아닌 500 Error가 적절함.
}

// 해당 계정이 갖고있는 권한 목록들을 리턴하는 역할
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.sajang.devracebackend.service;

import com.sajang.devracebackend.dto.auth.ReissueRequestDto;
import com.sajang.devracebackend.dto.auth.TokenDto;

public interface TokenService {
TokenDto reissue(ReissueRequestDto reissueRequestDto);
void updateRefreshToken(Long userId, String refreshToken);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package com.sajang.devracebackend.service.impl;

import com.sajang.devracebackend.domain.User;
import com.sajang.devracebackend.domain.enums.Role;
import com.sajang.devracebackend.dto.auth.ReissueRequestDto;
import com.sajang.devracebackend.dto.auth.TokenDto;
import com.sajang.devracebackend.response.exception.exception400.TokenBadRequestException;
import com.sajang.devracebackend.security.jwt.TokenProvider;
import com.sajang.devracebackend.service.TokenService;
import com.sajang.devracebackend.service.UserService;
import io.jsonwebtoken.JwtException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -12,8 +19,39 @@
public class TokenServiceImpl implements TokenService {

private final UserService userService;
private final TokenProvider tokenProvider;


@Transactional
@Override
public TokenDto reissue(ReissueRequestDto reissueRequestDto) { // Refresh Token으로 Access Token 재발급 메소드

// RequestDto로 전달받은 Token값들
String accessToken = reissueRequestDto.getAccessToken();
String refreshToken = reissueRequestDto.getRefreshToken();

// Refresh Token 유효성 검사
if(tokenProvider.validateToken(refreshToken) == false) {
throw new JwtException("입력한 Refresh Token은 잘못된 토큰입니다.");
}

// Access Token에서 userId 가져오기
Authentication authentication = tokenProvider.getAuthentication(accessToken);
Long userId = Long.valueOf(authentication.getName());

// userId로 사용자 검색 & 해당 사용자의 role 가져오기
User user = userService.findUser(userId);
Role role = user.getRole();

// DB의 사용자 Refresh Token 값과, 전달받은 Refresh Token의 불일치 여부 검사
if(!user.getRefreshToken().equals(refreshToken)) {
throw new TokenBadRequestException("Refresh Token = " + refreshToken);
}

TokenDto tokenDto = tokenProvider.generateAccessTokenByRefreshToken(userId, role, refreshToken);
return tokenDto;
}

@Transactional
@Override
public void updateRefreshToken(Long userId, String refreshToken) {
Expand Down

0 comments on commit fbf84fc

Please sign in to comment.