diff --git a/src/main/java/com/sajang/devracebackend/controller/AuthController.java b/src/main/java/com/sajang/devracebackend/controller/AuthController.java index 3b7eeeb..610abc4 100644 --- a/src/main/java/com/sajang/devracebackend/controller/AuthController.java +++ b/src/main/java/com/sajang/devracebackend/controller/AuthController.java @@ -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; @@ -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) @@ -35,4 +39,11 @@ public ResponseEntity> 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> reissue(@RequestBody ReissueRequestDto reissueRequestDto) { + TokenDto tokenDto = tokenService.reissue(reissueRequestDto); + return ResponseData.toResponseEntity(ResponseCode.REISSUE_SUCCESS, tokenDto); + } } diff --git a/src/main/java/com/sajang/devracebackend/dto/auth/ReissueRequestDto.java b/src/main/java/com/sajang/devracebackend/dto/auth/ReissueRequestDto.java new file mode 100644 index 0000000..43bc046 --- /dev/null +++ b/src/main/java/com/sajang/devracebackend/dto/auth/ReissueRequestDto.java @@ -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; +} diff --git a/src/main/java/com/sajang/devracebackend/response/GlobalExceptionHandler.java b/src/main/java/com/sajang/devracebackend/response/GlobalExceptionHandler.java index 4491fd4..68bb7b0 100644 --- a/src/main/java/com/sajang/devracebackend/response/GlobalExceptionHandler.java +++ b/src/main/java/com/sajang/devracebackend/response/GlobalExceptionHandler.java @@ -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()); diff --git a/src/main/java/com/sajang/devracebackend/response/ResponseCode.java b/src/main/java/com/sajang/devracebackend/response/ResponseCode.java index 96af482..ae45344 100644 --- a/src/main/java/com/sajang/devracebackend/response/ResponseCode.java +++ b/src/main/java/com/sajang/devracebackend/response/ResponseCode.java @@ -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), // ===================== // diff --git a/src/main/java/com/sajang/devracebackend/response/exception/exception400/TokenBadRequestException.java b/src/main/java/com/sajang/devracebackend/response/exception/exception400/TokenBadRequestException.java new file mode 100644 index 0000000..7cc5759 --- /dev/null +++ b/src/main/java/com/sajang/devracebackend/response/exception/exception400/TokenBadRequestException.java @@ -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); + } +} diff --git a/src/main/java/com/sajang/devracebackend/response/responseitem/MessageItem.java b/src/main/java/com/sajang/devracebackend/response/responseitem/MessageItem.java index c9b06e3..27a66b4 100644 --- a/src/main/java/com/sajang/devracebackend/response/responseitem/MessageItem.java +++ b/src/main/java/com/sajang/devracebackend/response/responseitem/MessageItem.java @@ -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 - 데이터 통합 조회 성공"; // 문제풀이 페이지 내 데이터들 조회 결과 응답시 사용할 예정. diff --git a/src/main/java/com/sajang/devracebackend/security/jwt/TokenProvider.java b/src/main/java/com/sajang/devracebackend/security/jwt/TokenProvider.java index 558b7cd..4dca323 100644 --- a/src/main/java/com/sajang/devracebackend/security/jwt/TokenProvider.java +++ b/src/main/java/com/sajang/devracebackend/security/jwt/TokenProvider.java @@ -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가 적절함. } // 해당 계정이 갖고있는 권한 목록들을 리턴하는 역할 diff --git a/src/main/java/com/sajang/devracebackend/service/TokenService.java b/src/main/java/com/sajang/devracebackend/service/TokenService.java index ba6f6f4..87d2fbd 100644 --- a/src/main/java/com/sajang/devracebackend/service/TokenService.java +++ b/src/main/java/com/sajang/devracebackend/service/TokenService.java @@ -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); } diff --git a/src/main/java/com/sajang/devracebackend/service/impl/TokenServiceImpl.java b/src/main/java/com/sajang/devracebackend/service/impl/TokenServiceImpl.java index 7898c6e..c325fa6 100644 --- a/src/main/java/com/sajang/devracebackend/service/impl/TokenServiceImpl.java +++ b/src/main/java/com/sajang/devracebackend/service/impl/TokenServiceImpl.java @@ -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; @@ -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) {