Skip to content

Commit

Permalink
feature: 토큰 재발급 구현
Browse files Browse the repository at this point in the history
related to: #11
  • Loading branch information
heejjinkim committed Sep 17, 2024
1 parent 74afe97 commit b96fba0
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public WebSecurityCustomizer webSecurityCustomizer() { // 정적 리소스 제
.requestMatchers("/css/**", "/images/**", "/js/**", "/lib/**")
.requestMatchers("/swagger-ui-custom.html", "/api-docs/**", "/swagger-ui/**",
"swagger-ui.html", "/v3/api-docs/**")
.requestMatchers("/error", "/favicon.ico");
.requestMatchers("/error", "/favicon.ico")
.requestMatchers("/members/reissue");
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public TokenInfo generateToken(String providerId, MemberRole memberRole) {
String accessToken = generateAccessToken(providerId, memberRole);
String refreshToken = generateRefreshToken();

// TODO: access 블랙리스트?
deleteInvalidRefreshToken(providerId);
redisUtil.setData(providerId, refreshToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

import com._119.wepro.member.domain.Member;
import com._119.wepro.member.domain.OauthInfo;
import io.lettuce.core.dynamic.annotation.Param;
import java.security.Provider;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByOauthInfo(OauthInfo oauthInfo);

@Query("SELECT m FROM Member m WHERE m.oauthInfo.providerId = :providerId")
Optional<Member> findByProviderId(@Param("providerId") String providerId);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package com._119.wepro.member.presentation;

import static com._119.wepro.global.security.constant.SecurityConstants.ACCESS_TOKEN_HEADER;
import static com._119.wepro.global.security.constant.SecurityConstants.REFRESH_TOKEN_HEADER;

import com._119.wepro.global.util.SecurityUtil;
import com._119.wepro.member.service.ReissueService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -13,9 +21,19 @@
public class memberController {

private final SecurityUtil securityUtil;
private final ReissueService reissueService;

@GetMapping("/me")
public ResponseEntity<Long> index() {
return ResponseEntity.ok(securityUtil.getCurrentMemberId());
}

@PostMapping("/reissue")
@Operation(summary = "access token 재발급")
public ResponseEntity<Void> refresh(
@RequestHeader(REFRESH_TOKEN_HEADER) String refreshToken,
@RequestHeader(ACCESS_TOKEN_HEADER) String accessToken, HttpServletResponse response) {
reissueService.reissue(refreshToken, accessToken, response);
return ResponseEntity.ok().build();
}
}
76 changes: 76 additions & 0 deletions src/main/java/com/_119/wepro/member/service/ReissueService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com._119.wepro.member.service;

import static com._119.wepro.global.exception.errorcode.CommonErrorCode.EXPIRED_TOKEN;
import static com._119.wepro.global.exception.errorcode.CommonErrorCode.INVALID_TOKEN;
import static com._119.wepro.global.exception.errorcode.CommonErrorCode.NOT_EXIST_BEARER_SUFFIX;
import static com._119.wepro.global.exception.errorcode.CommonErrorCode.REFRESH_DENIED;
import static com._119.wepro.global.security.constant.SecurityConstants.ACCESS_TOKEN_HEADER;
import static com._119.wepro.global.security.constant.SecurityConstants.GRANT_TYPE;
import static com._119.wepro.global.security.constant.SecurityConstants.REFRESH_TOKEN_HEADER;

import com._119.wepro.global.dto.TokenInfo;
import com._119.wepro.global.exception.RestApiException;
import com._119.wepro.global.exception.errorcode.UserErrorCode;
import com._119.wepro.global.security.JwtTokenProvider;
import com._119.wepro.member.domain.Member;
import com._119.wepro.member.domain.repository.MemberRepository;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ReissueService {

private final JwtTokenProvider jwtTokenProvider;
private final MemberRepository memberRepository;

public void reissue(String refreshToken, String accessToken, HttpServletResponse response) {
refreshToken = extractToken(refreshToken);
accessToken = extractToken(accessToken);

validateAccessTokenExpired(accessToken);
String providerId = jwtTokenProvider.parseExpiredToken(accessToken).getSubject();

validateRefreshToken(refreshToken, providerId);

Member member = memberRepository.findByProviderId(providerId)
.orElseThrow(() -> new RestApiException(UserErrorCode.USER_NOT_FOUND));

TokenInfo newTokenInfo = jwtTokenProvider.generateToken(providerId, member.getRole());
setTokenPairToResponseHeader(response, newTokenInfo.getAccessToken(),
newTokenInfo.getRefreshToken());
}

private String extractToken(String token) {
if (!token.startsWith(GRANT_TYPE)) {
throw new RestApiException(NOT_EXIST_BEARER_SUFFIX);
}

return token.replace(GRANT_TYPE, "");
}

private void validateAccessTokenExpired(String accessToken) {
try {
jwtTokenProvider.validateToken(accessToken);
throw new RestApiException(REFRESH_DENIED);
} catch (RestApiException e) {
if (e.getErrorCode() != EXPIRED_TOKEN) {
throw e;
}
}
}

private void validateRefreshToken(String refreshToken, String memberId) {
String savedRefreshToken = jwtTokenProvider.getRefreshToken(memberId);
if (!refreshToken.equals(savedRefreshToken)) {
throw new RestApiException(INVALID_TOKEN);
}
}

private void setTokenPairToResponseHeader(
HttpServletResponse response, String accessToken, String refreshToken) {
response.setHeader(ACCESS_TOKEN_HEADER, GRANT_TYPE + accessToken);
response.setHeader(REFRESH_TOKEN_HEADER, GRANT_TYPE + refreshToken);
}
}

0 comments on commit b96fba0

Please sign in to comment.