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

[feat] #274 - @DisableSwaggerSecurity 어노테이션 추가 및 리프레쉬 토큰을 통한 액세스 토큰 발급 로직 변경 #275

Merged
merged 6 commits into from
Dec 2, 2024

Conversation

hoonyworld
Copy link
Member

@hoonyworld hoonyworld commented Dec 2, 2024

Related issue 🛠

Work Description ✏️

참고링크

@DisableSwaggerSecurity 커스텀 어노테이션으로 Swagger UI에서 보안설정 비활성화

  • @DisableSwaggerSecurity 커스텀 어노테이션을 추가하여 Swagger UI에서 특정 API의 보안 설정을 비활성화할 수 있도록 구현하였습니다.
  • 이를 통해 클라이언트는 API 문서를 확인하면서 인증이 필요한 API와 그렇지 않은 API를 명확히 구분할 수 있어 가독성과 사용성을 높일 수 있습니다.

리프레쉬 토큰을 통한 액세스 토큰 발급 로직 변경

  • 기존에는 RequestParam으로 리프레시 토큰을 받아 액세스 토큰을 재발급 받도록 하였습니다. 하지만, 해당 방법은 토큰이 URL에 노출되기 때문에 보안상 취약점이 있었습니다.
  • 이를 개선하기 위해, RequestHeader를 통해 리프레시 토큰을 전달받도록 수정하였습니다.

Trouble Shooting ⚽️

Authorization 헤더 중복 처리 문제

image
image
image


image
  • @RequestHeader("Authorization")은 기본적으로 액세스 토큰을 다루기 위한 로직이고, 그에 따라 JwtAuthenticationFilter에서 이를 처리하도록 설계된 상황입니다.
  • 따라서 Refresh Token 요청 시에도 동일한 헤더(Authorization)를 사용하는 경우, JwtAuthenticationFilter가 이를 처리하려 하면서 중복으로 처리가 되는 문제가 발생했습니다.

Authorization 헤더 중복 처리 문제 해결

image
image
image

  • Authorization_Refresh라는 별도의 헤더명을 사용해 Refresh Token 요청을 구분하면 문제를 간단히 해결하였습니다.
  • Bearer 접두사는 액세스 토큰을 헤더에 담을 경우 JwtAuthenticationFilter 제거해주지만, 리프레쉬 토큰을 헤더에 담을 경우 AuthenticationServicegenerateAccessTokenFromRefreshToken에서 Bearer 접두사를 제거해줍니다.
@Transactional
public AccessTokenGetSuccess generateAccessTokenFromRefreshToken(final String refreshTokenWithBearer) {
String refreshToken = refreshTokenWithBearer;
if (refreshToken.startsWith(BEARER_PREFIX)) {  // 해당 부분에서 Bearer 접두사 제거 
	refreshToken = refreshToken.substring(BEARER_PREFIX.length());
}

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

JwtValidationType validationType = jwtTokenProvider.validateToken(refreshToken);
if (!validationType.equals(JwtValidationType.VALID_JWT)) {
	log.warn("Invalid refresh token: {}", validationType);
	throw switch (validationType) {
		case EXPIRED_JWT_TOKEN -> new UnauthorizedException(TokenErrorCode.REFRESH_TOKEN_EXPIRED_ERROR);
		case INVALID_JWT_TOKEN -> new BadRequestException(TokenErrorCode.INVALID_REFRESH_TOKEN_ERROR);
		case INVALID_JWT_SIGNATURE -> new BadRequestException(TokenErrorCode.REFRESH_TOKEN_SIGNATURE_ERROR);
		case UNSUPPORTED_JWT_TOKEN -> new BadRequestException(TokenErrorCode.UNSUPPORTED_REFRESH_TOKEN_ERROR);
		case EMPTY_JWT -> new BadRequestException(TokenErrorCode.REFRESH_TOKEN_EMPTY_ERROR);
		default -> new BeatException(TokenErrorCode.UNKNOWN_REFRESH_TOKEN_ERROR);
	};
}

Long memberId = jwtTokenProvider.getMemberIdFromJwt(refreshToken);

if (!memberId.equals(tokenService.findIdByRefreshToken(refreshToken))) {
	log.error("MemberId mismatch: token does not match the stored refresh token");
	throw new BadRequestException(TokenErrorCode.REFRESH_TOKEN_MEMBER_ID_MISMATCH_ERROR);
}

Role role = jwtTokenProvider.getRoleFromJwt(refreshToken);
Collection<GrantedAuthority> authorities = List.of(role.toGrantedAuthority());

UsernamePasswordAuthenticationToken authenticationToken = createAuthenticationToken(memberId, role,
	authorities);
log.info("Generated new access token for memberId: {}, role: {}, authorities: {}",
	memberId, role.getRoleName(), authorities);
return AccessTokenGetSuccess.of(jwtTokenProvider.issueAccessToken(authenticationToken));
}

Related ScreenShot 📷

image
  • 인증이 필요없는 API의 경우 좌물쇠가 표시되지 않는 것을 확인하실 수 있습니다.
image
  • Header에 Authorization_Refresh를 명시하고 해당 value로 리프레쉬 토큰 값을 넣어주면 성공적으로 액세스 토큰이 발급되어짐을 확인했습니다.(하단 data에 있는 accessToken값은 보안상의 이유로 캡쳐를 하지 않았습니다)

Uncompleted Tasks 😅

To Reviewers 📢

}

private Info apiInfo() {
return new Info()
.title("BEAT Project API")
.description("간편하게 소규모 공연을 등록하고 관리할 수 있는 티켓 예매 플랫폼")
.version("1.1.0");
.version("1.2.0");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디테일 굿입니다 👍

Copy link
Collaborator

@hyerinhwang-sailin hyerinhwang-sailin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swagger에서 각 api들의 인증 필요 여부를 명시적으로 표현하는 것,
보안을 위해 refreshToken을 request param이 아닌 header로 받고 accessToken과 분리된 로직으로 처리하는 것 모두 좋네요!! 고생하셨습니다 👍

@hoonyworld hoonyworld merged commit 578ab06 into develop Dec 2, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants