Skip to content

Commit

Permalink
[feat] #274 - @DisableSwaggerSecurity 어노테이션 추가 및 리프레쉬 토큰을 통한 액세스 토큰 발…
Browse files Browse the repository at this point in the history
…급 로직 변경 (#275)

* [#274] chore(JwtTokenProvider): public 메서드를 상단으로 배치

* [#274] feat(DisableSwaggerSecurity): Swagger 보안 비활성화 어노테이션 추가

* [#274] feat(SwaggerConfig): Swagger UI에서 보안 설정을 제거하는 OperationCustomizer 구현

* [#274] feat: Swagger 보안을 비활성화할 메서드 지정

* [#274] feat: 로그아웃 API에서 매개변수를 principal 객체에서 memberId로 변경

* [#274] feat(AuthenticationService): 헤더로 받은 리프레시 토큰의 Bearer 접두사를 제거하는 로직 추가
  • Loading branch information
hoonyworld authored Dec 2, 2024
1 parent 8e81a1f commit 578ab06
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 78 deletions.
5 changes: 5 additions & 0 deletions src/main/java/com/beat/domain/booking/api/BookingApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.beat.global.auth.annotation.CurrentMember;
import com.beat.global.common.dto.ErrorResponse;
import com.beat.global.common.dto.SuccessResponse;
import com.beat.global.swagger.annotation.DisableSwaggerSecurity;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -92,6 +93,7 @@ ResponseEntity<SuccessResponse<List<MemberBookingRetrieveResponse>>> getMemberBo
@CurrentMember Long memberId
);

@DisableSwaggerSecurity
@Operation(summary = "비회원 예매 API", description = "비회원이 예매를 요청하는 POST API입니다.")
@ApiResponses(
value = {
Expand Down Expand Up @@ -125,6 +127,7 @@ ResponseEntity<SuccessResponse<GuestBookingResponse>> createGuestBookings(
@RequestBody GuestBookingRequest guestBookingRequest
);

@DisableSwaggerSecurity
@Operation(summary = "비회원 예매 조회 API", description = "비회원이 예매를 조회하는 POST API입니다.")
@ApiResponses(
value = {
Expand All @@ -143,6 +146,7 @@ ResponseEntity<SuccessResponse<List<GuestBookingRetrieveResponse>>> getGuestBook
@RequestBody GuestBookingRetrieveRequest guestBookingRetrieveRequest
);

@DisableSwaggerSecurity
@Operation(summary = "유료공연 예매 환불 요청 API", description = "유료공연 예매자가 환불 요청하는 PATCH API입니다.")
@ApiResponses(
value = {
Expand All @@ -161,6 +165,7 @@ ResponseEntity<SuccessResponse<BookingRefundResponse>> refundBookings(
@RequestBody BookingRefundRequest bookingRefundRequest
);

@DisableSwaggerSecurity
@Operation(summary = "무료공연/미입금 예매 취소 요청 API", description = "무료공연/미입금 예매자가 취소 요청하는 PATCH API입니다.")
@ApiResponses(
value = {
Expand Down
20 changes: 13 additions & 7 deletions src/main/java/com/beat/domain/member/api/MemberApi.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.beat.domain.member.api;

import java.security.Principal;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;

import com.beat.domain.member.dto.AccessTokenGetSuccess;
import com.beat.domain.member.dto.LoginSuccessResponse;
import com.beat.global.auth.annotation.CurrentMember;
import com.beat.global.auth.client.dto.MemberLoginRequest;
import com.beat.global.common.dto.ErrorResponse;
import com.beat.global.common.dto.SuccessResponse;
import com.beat.global.swagger.annotation.DisableSwaggerSecurity;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -21,6 +24,7 @@
@Tag(name = "Member", description = "회원 관련 API")
public interface MemberApi {

@DisableSwaggerSecurity
@Operation(summary = "로그인/회원가입 API", description = "로그인/회원가입하는 POST API입니다.")
@ApiResponses(
value = {
Expand All @@ -41,8 +45,8 @@ public interface MemberApi {
}
)
ResponseEntity<SuccessResponse<LoginSuccessResponse>> signUp(
String authorizationCode,
MemberLoginRequest loginRequest,
@RequestParam final String authorizationCode,
@RequestBody final MemberLoginRequest loginRequest,
HttpServletResponse response
);

Expand All @@ -60,8 +64,8 @@ ResponseEntity<SuccessResponse<LoginSuccessResponse>> signUp(
)
}
)
ResponseEntity<SuccessResponse<AccessTokenGetSuccess>> refreshToken(
String refreshToken
ResponseEntity<SuccessResponse<AccessTokenGetSuccess>> issueAccessTokenUsingRefreshToken(
@RequestHeader("Authorization_Refresh") final String refreshToken
);

@Operation(summary = "로그아웃 API", description = "로그아웃하는 POST API입니다.")
Expand All @@ -78,6 +82,8 @@ ResponseEntity<SuccessResponse<AccessTokenGetSuccess>> refreshToken(
)
}
)
ResponseEntity<SuccessResponse<Void>> signOut(Principal principal);
ResponseEntity<SuccessResponse<Void>> signOut(
@CurrentMember final Long memberId
);
}

26 changes: 12 additions & 14 deletions src/main/java/com/beat/domain/member/api/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.beat.domain.member.api;

import java.security.Principal;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -17,6 +15,7 @@
import com.beat.domain.member.dto.AccessTokenGetSuccess;
import com.beat.domain.member.dto.LoginSuccessResponse;
import com.beat.domain.member.exception.MemberSuccessCode;
import com.beat.global.auth.annotation.CurrentMember;
import com.beat.global.auth.client.dto.MemberLoginRequest;
import com.beat.global.auth.jwt.application.TokenService;
import com.beat.global.common.dto.SuccessResponse;
Expand Down Expand Up @@ -52,30 +51,29 @@ public ResponseEntity<SuccessResponse<LoginSuccessResponse>> signUp(
.httpOnly(true)
.build();
response.setHeader("Set-Cookie", cookie.toString());
return ResponseEntity.status(HttpStatus.OK)
return ResponseEntity.ok()
.body(SuccessResponse.of(MemberSuccessCode.SIGN_UP_SUCCESS,
LoginSuccessResponse.of(loginSuccessResponse.accessToken(), null, loginSuccessResponse.nickname(),
loginSuccessResponse.role())));
}

@Override
@GetMapping("/refresh-token")
public ResponseEntity<SuccessResponse<AccessTokenGetSuccess>> refreshToken(
@RequestParam final String refreshToken
public ResponseEntity<SuccessResponse<AccessTokenGetSuccess>> issueAccessTokenUsingRefreshToken(
@RequestHeader("Authorization_Refresh") final String refreshToken
) {
AccessTokenGetSuccess accessTokenGetSuccess = authenticationService.generateAccessTokenFromRefreshToken(
refreshToken);
return ResponseEntity.status(HttpStatus.OK)
.body(SuccessResponse.of(MemberSuccessCode.ISSUE_REFRESH_TOKEN_SUCCESS, accessTokenGetSuccess));
AccessTokenGetSuccess accessTokenGetSuccess = authenticationService.generateAccessTokenFromRefreshToken(refreshToken);
return ResponseEntity.ok()
.body(SuccessResponse.of(MemberSuccessCode.ISSUE_ACCESS_TOKEN_USING_REFRESH_TOKEN, accessTokenGetSuccess));
}

@Override
@PostMapping("/sign-out")
public ResponseEntity<SuccessResponse<Void>> signOut(
final Principal principal
@CurrentMember final Long memberId
) {
tokenService.deleteRefreshToken(Long.valueOf(principal.getName()));
return ResponseEntity.status(HttpStatus.OK)
tokenService.deleteRefreshToken(memberId);
return ResponseEntity.ok()
.body(SuccessResponse.from(MemberSuccessCode.SIGN_OUT_SUCCESS));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
@Service
@RequiredArgsConstructor
public class AuthenticationService {

private static final String BEARER_PREFIX = "Bearer ";
private final JwtTokenProvider jwtTokenProvider;
private final TokenService tokenService;

Expand Down Expand Up @@ -69,11 +69,16 @@ public LoginSuccessResponse generateLoginSuccessResponse(final Long memberId, fi
* Refresh Token에서 사용자 ID와 Role 정보를 추출한 후,
* Role에 따라 Admin 또는 Member 권한으로 새로운 Access Token을 발급합니다.
*
* @param refreshToken 사용자의 Refresh Token
* @param refreshTokenWithBearer "Bearer + 사용자의 Refresh Token"
* @return 새로운 Access Token 정보가 포함된 AccessTokenGetSuccess 객체
*/
@Transactional
public AccessTokenGetSuccess generateAccessTokenFromRefreshToken(final String refreshToken) {
public AccessTokenGetSuccess generateAccessTokenFromRefreshToken(final String refreshTokenWithBearer) {
String refreshToken = refreshTokenWithBearer;
if (refreshToken.startsWith(BEARER_PREFIX)) {
refreshToken = refreshToken.substring(BEARER_PREFIX.length());
}

log.info("Validation result for refresh token: {}", jwtTokenProvider.validateToken(refreshToken));

JwtValidationType validationType = jwtTokenProvider.validateToken(refreshToken);
Expand Down Expand Up @@ -139,4 +144,4 @@ private UsernamePasswordAuthenticationToken createAuthenticationToken(Long membe
return new MemberAuthentication(memberId, null, authorities);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public enum MemberSuccessCode implements BaseSuccessCode {
*/
SIGN_UP_SUCCESS(200, "로그인 성공"),
ISSUE_ACCESS_TOKEN_SUCCESS(200, "엑세스토큰 발급 성공"),
ISSUE_REFRESH_TOKEN_SUCCESS(200, "리프레쉬토큰 발급 성공"),
ISSUE_ACCESS_TOKEN_USING_REFRESH_TOKEN(200, "리프레쉬 토큰으로 액세스 토큰 재발급 성공"),
SIGN_OUT_SUCCESS(200, "로그아웃 성공"),
USER_DELETE_SUCCESS(200, "회원 탈퇴 성공");

Expand Down
5 changes: 2 additions & 3 deletions src/main/java/com/beat/domain/performance/api/HomeApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@

import com.beat.domain.performance.application.dto.home.HomeFindAllResponse;
import com.beat.domain.performance.domain.Genre;
import com.beat.global.common.dto.ErrorResponse;
import com.beat.global.common.dto.SuccessResponse;
import com.beat.global.swagger.annotation.DisableSwaggerSecurity;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "Home", description = "홈 화면에서 공연 및 홍보목록 조회 API")
public interface HomeApi {

@DisableSwaggerSecurity
@Operation(summary = "전체 공연 및 홍보 목록 조회", description = "홈 화면에서 전체 공연 목록 및 홍보 목록을 조회하는 GET API")
@ApiResponses(
value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.beat.global.auth.annotation.CurrentMember;
import com.beat.global.common.dto.ErrorResponse;
import com.beat.global.common.dto.SuccessResponse;
import com.beat.global.swagger.annotation.DisableSwaggerSecurity;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -108,6 +109,7 @@ ResponseEntity<SuccessResponse<PerformanceModifyDetailResponse>> getPerformanceF
@PathVariable Long performanceId
);

@DisableSwaggerSecurity
@Operation(summary = "공연 상세정보 조회 API", description = "공연 상세페이지의 공연 상세정보를 조회하는 GET API입니다.")
@ApiResponses(
value = {
Expand All @@ -126,6 +128,7 @@ ResponseEntity<SuccessResponse<PerformanceDetailResponse>> getPerformanceDetail(
@PathVariable Long performanceId
);

@DisableSwaggerSecurity
@Operation(summary = "예매하기 관련 공연 정보 조회 API", description = "예매하기 페이지에서 필요한 예매 관련 공연 정보를 조회하는 GET API입니다.")
@ApiResponses(
value = {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/beat/domain/schedule/api/ScheduleApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.beat.domain.schedule.application.dto.TicketAvailabilityResponse;
import com.beat.global.common.dto.ErrorResponse;
import com.beat.global.common.dto.SuccessResponse;
import com.beat.global.swagger.annotation.DisableSwaggerSecurity;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -18,6 +19,7 @@
@Tag(name = "Schedule", description = "스케줄 관련 API")
public interface ScheduleApi {

@DisableSwaggerSecurity
@Operation(summary = "티켓 구매 가능 여부 조회 API", description = "티켓 구매 가능 여부를 확인하는 GET API입니다.")
@ApiResponses(
value = {
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/com/beat/domain/user/api/HealthCheckApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.springframework.web.bind.annotation.GetMapping;

import com.beat.global.swagger.annotation.DisableSwaggerSecurity;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
Expand All @@ -10,15 +12,12 @@
@Tag(name = "Health-Check", description = "헬스 체크 API")
public interface HealthCheckApi {

@Operation(
summary = "헬스 체크 조회 API",
description = "서버 상태를 확인하기 위한 헬스 체크 API로, 정상적으로 동작할 경우 'OK' 문자열을 반환합니다."
)
@DisableSwaggerSecurity
@Operation(summary = "헬스 체크 조회 API", description = "서버 상태를 확인하기 위한 헬스 체크 API로, 정상적으로 동작할 경우 'OK' 문자열을 반환합니다.")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "서버가 정상적으로 동작 중입니다.")
}
)
@GetMapping
String healthcheck();
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,38 +54,6 @@ public String issueRefreshToken(final Authentication authentication) {
return issueToken(authentication, refreshTokenExpireTime);
}

private String issueToken(final Authentication authentication, final long expiredTime) {
final Date now = new Date();

final Claims claims = Jwts.claims().setIssuedAt(now).setExpiration(new Date(now.getTime() + expiredTime));

claims.put(MEMBER_ID, authentication.getPrincipal());
log.info("Added member ID to claims: {}", authentication.getPrincipal());
log.info("Authorities before token generation: {}", authentication.getAuthorities());

String role = authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No authorities found for user"));

log.info("Selected role for token: {}", role);

claims.put(ROLE_KEY, role);
log.info("Added role to claims: {}", role);

return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setClaims(claims)
.signWith(getSigningKey())
.compact();
}

private SecretKey getSigningKey() {
String encodedKey = Base64.getEncoder().encodeToString(jwtSecret.getBytes());
return Keys.hmacShaKeyFor(encodedKey.getBytes());
}

public JwtValidationType validateToken(String token) {
try {
Claims claims = getBody(token);
Expand All @@ -108,10 +76,6 @@ public JwtValidationType validateToken(String token) {
}
}

private Claims getBody(final String token) {
return Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
}

public Long getMemberIdFromJwt(String token) {
Claims claims = getBody(token);
Long memberId = Long.valueOf(claims.get(MEMBER_ID).toString());
Expand All @@ -134,4 +98,40 @@ public Role getRoleFromJwt(String token) {

return Role.valueOf(enumValue.toUpperCase());
}
}

private String issueToken(final Authentication authentication, final long expiredTime) {
final Date now = new Date();

final Claims claims = Jwts.claims().setIssuedAt(now).setExpiration(new Date(now.getTime() + expiredTime));

claims.put(MEMBER_ID, authentication.getPrincipal());
log.info("Added member ID to claims: {}", authentication.getPrincipal());
log.info("Authorities before token generation: {}", authentication.getAuthorities());

String role = authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No authorities found for user"));

log.info("Selected role for token: {}", role);

claims.put(ROLE_KEY, role);
log.info("Added role to claims: {}", role);

return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setClaims(claims)
.signWith(getSigningKey())
.compact();
}

private Claims getBody(final String token) {
return Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
}

private SecretKey getSigningKey() {
String encodedKey = Base64.getEncoder().encodeToString(jwtSecret.getBytes());
return Keys.hmacShaKeyFor(encodedKey.getBytes());
}
}
Loading

0 comments on commit 578ab06

Please sign in to comment.