From 46e4dff6840c4b46d5ff5d60398cb929cc089f3a Mon Sep 17 00:00:00 2001 From: JungYoonShin Date: Sun, 2 Jun 2024 23:17:00 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT=20#9]:=20ATK=20=EC=9E=AC=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/seminar/common/dto/ErrorMessage.java | 3 ++ .../seminar/controller/MemberController.java | 10 ++++- .../sopt/seminar/jwt/JwtTokenProvider.java | 9 ++++ .../seminar/jwt/config/SecurityConfig.java | 2 +- .../CustomAuthenticationSuccessHandler.java | 42 ------------------- .../sopt/seminar/service/MemberService.java | 26 ++++++++++-- 6 files changed, 43 insertions(+), 49 deletions(-) delete mode 100644 week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/handler/CustomAuthenticationSuccessHandler.java diff --git a/week_6/6thSeminar/src/main/java/com/sopt/seminar/common/dto/ErrorMessage.java b/week_6/6thSeminar/src/main/java/com/sopt/seminar/common/dto/ErrorMessage.java index 534dee8..6fb0c88 100644 --- a/week_6/6thSeminar/src/main/java/com/sopt/seminar/common/dto/ErrorMessage.java +++ b/week_6/6thSeminar/src/main/java/com/sopt/seminar/common/dto/ErrorMessage.java @@ -12,6 +12,9 @@ public enum ErrorMessage { MEMBER_NOT_FOUND(HttpStatus.NO_CONTENT.value(), "ID에 해당하는 사용자가 존재하지 않습니다."), BLOG_NOT_FOUND(HttpStatus.NO_CONTENT.value(), "ID에 해당하는 블로그가 존재하지 않습니다."), JWT_UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "사용자의 로그인 검증을 실패했습니다."), + EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 만료되었습니다. 재로그인해주세요."), + EXPIRED_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED.value(), "엑세스 토큰이 만료되었습니다. 재발급 받아주세요."), + MISMATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(), "실제 리프레시 토큰과 일치하지 않습니다. 재로그인해주세요.") ; private final int status; diff --git a/week_6/6thSeminar/src/main/java/com/sopt/seminar/controller/MemberController.java b/week_6/6thSeminar/src/main/java/com/sopt/seminar/controller/MemberController.java index 750e48c..91f65a1 100644 --- a/week_6/6thSeminar/src/main/java/com/sopt/seminar/controller/MemberController.java +++ b/week_6/6thSeminar/src/main/java/com/sopt/seminar/controller/MemberController.java @@ -1,5 +1,6 @@ package com.sopt.seminar.controller; +import com.sopt.seminar.jwt.TokenInfo; import com.sopt.seminar.service.dto.UserJoinResponse; import com.sopt.seminar.global.ApiResponse; import com.sopt.seminar.global.ApiUtils; @@ -13,12 +14,12 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/members") //v1은 버전관리할 때 이렇게 쓰곤 함 +@RequestMapping("/api/v1") //v1은 버전관리할 때 이렇게 쓰곤 함 public class MemberController { private final MemberService memberService; - @PostMapping + @PostMapping("/members") public ResponseEntity postMember( @RequestBody @Valid MemberCreateRequest memberCreate ) { @@ -29,6 +30,11 @@ public ResponseEntity postMember( ); } + @GetMapping("/members/reissue") + public ResponseEntity reissue(@RequestHeader String refreshToken) { + return ResponseEntity.ok(memberService.reissue(refreshToken)); + } + @GetMapping("/{memberId}") public ResponseEntity> findMemberById(@PathVariable("memberId") Long memberId) { return ApiUtils.success(HttpStatus.OK, memberService.findMemberById(memberId)); diff --git a/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/JwtTokenProvider.java b/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/JwtTokenProvider.java index 2dc8024..b9bb24d 100644 --- a/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/JwtTokenProvider.java +++ b/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/JwtTokenProvider.java @@ -1,5 +1,7 @@ package com.sopt.seminar.jwt; +import com.sopt.seminar.common.dto.ErrorMessage; +import com.sopt.seminar.exception.UnauthorizedException; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import lombok.RequiredArgsConstructor; @@ -73,6 +75,13 @@ private Claims getBody(final String token) { .getBody(); } + public void matchRefreshToken(String refreshToken, String storedRefreshToken) { + if (!refreshToken.equals(storedRefreshToken)) { + throw new UnauthorizedException(ErrorMessage.MISMATCH_REFRESH_TOKEN); + } + } + + public String getUserFromJwt(String token) { Claims claims = getBody(token); return claims.get(EMAIL).toString(); diff --git a/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/config/SecurityConfig.java b/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/config/SecurityConfig.java index 7d2f443..3c26be7 100644 --- a/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/config/SecurityConfig.java +++ b/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/config/SecurityConfig.java @@ -25,7 +25,7 @@ public class SecurityConfig { private final CustomAccessDeniedHandler customAccessDeniedHandler; - private static final String[] AUTH_WHITE_LIST = {"/api/v1/members", "/"}; + private static final String[] AUTH_WHITE_LIST = {"/api/v1/members", "/", "/api/v1/members/reissue"}; @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { diff --git a/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/handler/CustomAuthenticationSuccessHandler.java b/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/handler/CustomAuthenticationSuccessHandler.java deleted file mode 100644 index 1d927bc..0000000 --- a/week_6/6thSeminar/src/main/java/com/sopt/seminar/jwt/handler/CustomAuthenticationSuccessHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.sopt.seminar.jwt.handler; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.sopt.seminar.jwt.JwtTokenProvider; -import com.sopt.seminar.jwt.TokenInfo; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.io.IOException; - -import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; - - -@Component -@RequiredArgsConstructor -public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - - private final JwtTokenProvider jwtTokenProvider; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - - //atk, rtk 발급 - response.setContentType(APPLICATION_JSON_VALUE); - - //refreshToken 업데이트(나중에 건드리기..) -// member.updateRefreshToken(tokenInfo.getRefreshToken()); - - new ObjectMapper().writeValue( - response.getWriter(), - TokenInfo.of(jwtTokenProvider.issueAccessToken(authentication), jwtTokenProvider.issueRefreshToken(authentication)) - ); - } -} diff --git a/week_6/6thSeminar/src/main/java/com/sopt/seminar/service/MemberService.java b/week_6/6thSeminar/src/main/java/com/sopt/seminar/service/MemberService.java index fcdeab9..159e1e8 100644 --- a/week_6/6thSeminar/src/main/java/com/sopt/seminar/service/MemberService.java +++ b/week_6/6thSeminar/src/main/java/com/sopt/seminar/service/MemberService.java @@ -1,6 +1,7 @@ package com.sopt.seminar.service; import com.sopt.seminar.common.dto.ErrorMessage; +import com.sopt.seminar.exception.UnauthorizedException; import com.sopt.seminar.jwt.JwtTokenProvider; import com.sopt.seminar.jwt.TokenInfo; import com.sopt.seminar.jwt.UserAuthentication; @@ -43,10 +44,22 @@ public UserJoinResponse createMember( UserAuthentication userAuthentication = UserAuthentication.createUserAuthentication(memberCreate.email()); TokenInfo token = issueTokenAndStoreRefreshToken(userAuthentication, member.getId()); - return UserJoinResponse.of( - token.accessToken(), - token.refreshToken() - ); + return UserJoinResponse.of(token.accessToken(), token.refreshToken()); + } + + public TokenInfo reissue(String refreshToken) { + jwtTokenProvider.validateToken(refreshToken); + String userEmail = jwtTokenProvider.getUserFromJwt(refreshToken); + System.out.println("userEmail = " + userEmail); + Member member = findMember(userEmail); + + //리프레시 토큰 탈취여부 확인(탈취범이 정상 유저보다 먼저 재발급 받았을 경우 -> 재로그인 유도) + jwtTokenProvider.matchRefreshToken(refreshToken, findRefreshToken(member.getId()).getRefreshToken()); + + UserAuthentication userAuthentication = UserAuthentication.createUserAuthentication(userEmail); + TokenInfo token = issueTokenAndStoreRefreshToken(userAuthentication, member.getId()); + + return token; } public MemberDetailResponse findMemberById(Long memberId) { @@ -81,6 +94,11 @@ public Member findMember(String email) { .orElseThrow(()-> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND)); } + private RefreshToken findRefreshToken(Long userId) { + return refreshTokenRepository.findById(userId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.EXPIRED_REFRESH_TOKEN)); + } + private TokenInfo issueTokenAndStoreRefreshToken(Authentication authentication, Long userId) { TokenInfo issuedToken = jwtTokenProvider.issueToken(authentication); RefreshToken refreshToken = RefreshToken.builder()