Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#33] Feat: JWT 토큰 만료 reissue 재발급 기능 완성 #38

Merged
merged 1 commit into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading