Skip to content

Conversation

@chemistryx
Copy link

해윤님 안녕하세요! 지난 사다리 미션에 이어 오랜만에 리뷰어로써 뵙는 것 같은데, 이번 스프링 미션도 잘 부탁드리겠습니다 ㅎㅎ..
이번 미션에서는 기존 단계에서 구현한 기능들에 인증 / 인가 기능을 추가하여, 특정 사용자만이 의도된 기능을 사용할 수 있도록 구현해보았습니다.

인증의 경우 사용자로부터 이메일, 비밀번호를 입력 받아 JWT를 통해 토큰을 생성한 후, 이를 기반으로 인증을 수행하도록 구현하였으며, 인가의 경우(관리자 기능) HandlerInterceptor를 활용하여 특정 역할(Role)이 있는 사용자만 정해진 endpoint에 접근할 수 있도록 제한하는 기능을 추가했습니다.

프로젝트 구조는 다음과 같습니다:

auth/
- AdminRoute - 관리자 endpoint를 구분하기 위한 어노테이션
- AdminRouteInterceptor - AdminRoute 어노테이션 구현
- JwtTokenProvider - JWT 관련 유틸리티 메소드
- LoginMember - 쿠키로부터 사용자 객체를 불러오는 커스텀 ArgumentResolver 어노테이션
- LoginMemberArgumentResolver - LoginMember 어노테이션 구현
config/
- WebConfig - WebMvcConfigurer 구현체
controller/
- MemberController - 사용자 관련 endpoint 정의
- ReservationController - 예약 관련 endpoint 정의
- ThemeController - 테마 관련 endpoint 정의
- TimeController - 시간 관련 endpoint 정의
- ViewController - thymeleaf 템플릿 관련 endpoint 정의
dao/
- MemberDao - 사용자 관련 DB 로직 정의
- ReservationDao - 예약 관련 DB 로직 정의
- ThemeDao - 테마 관련 DB 로직 정의
- TimeDao - 시간 관련 DB 로직 정의
dto/
- ...(계층 간 데이터 전송에 필요한 객체들 정의)
exception/
- GlobalExceptionHandler - 공통 예외 처리 로직
- UnauthorizedException - 인증되지 않은 사용자에 대한 커스텀 예외
model/
- ...(DB 테이블 스키마 정의)
service/
- AuthService - 사용자 인증 관련 로직 정의
- MemberService - 사용자 관련 로직 정의
- ReservationService - 예약 관련 로직 정의
- TimeService - 시간 관련 로직 정의
RoomescapeApplication - Main entrypoint

다음은 구현 중 발생한 질문 사항입니다.

  1. 현재 DB에 등록되지 않은 사용자 정보로 로그인 시도 시에도 메인 페이지로 리디렉션이 발생하고 있습니다. 코드 상에서 보았을 때는 로그인 실패 시 Login failed 알림 창이 발생하고 따로 리디렉션은 이루어지지 않아야 할 것 같은데, 이 부분은 어떻게 해결할 수 있을까요..?

  2. 관리자만 관리자 endpoint에 접근할 수 있도록 @AdminRoute 어노테이션을 통해 적용해보았는데, 지금 방식의 경우 각 메소드 / 클래스마다 일일히 어노테이션을 붙여주어야만 작동하도록 구현되어있습니다. 따라서 해당 어노테이션을 까먹고 적용하지 않는 경우 문제가 될 수 있을 것 같은데, 이러한 경우를 방지하려면 어떠한 방식으로 접근하는게 좋을까요?

긴 글 읽어주셔서 감사드리며, 코드 관련해서 궁금하신 점이나 다른 사항이 있으시다면 언제든지 편하게 말씀해주세요~! 🙃🙃

Copy link

@haeyoon1 haeyoon1 left a comment

Choose a reason for hiding this comment

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

안녕하세요 수한님! 오랜만에 다시 만나뵙게되어 반갑습니다~ 잘 부탁드려요🙇‍♀️🙇‍♀️🙇‍♀️
리뷰에 개인적인 코드 취향들도 섞여있어서 이야기 나눠보고 선택적으로 반영해주시면 좋을 것 같아요👍

질문 및 피드백

입력값 검증의 위치save 반환형에 대한 리뷰는 코드 전반에 달지 않고 하나에만 달았는데, 수정하신다면 해당 도메인 부분만이 아닌 전반적으로 수정해주셔야할 것 같아요! 코멘트는 하나의 도메인에 대해서만 남겼습니다!

  1. 프로젝트 구조를 대표적으로 계층형(현재 작성해주신 구조) vs 도메인형 이렇게 나눌 수 있을 것 같은데, 계층형으로 작성하신 이유가 궁금합니다!
  2. 또한 전반적으로 에러 상황에 대한 응답 포맷이 상태 코드로만 내려가고 있는데, 공통 에러 응답 포맷을 정의하면 더 일관된 에러 처리를 할 수 있을 것 같아요!

Qna

  1. 현재 DB에 등록되지 않은 사용자 정보로 로그인 시도 시에도 메인 페이지로 리디렉션이 발생하고 있습니다. 코드 상에서 보았을 때는 로그인 실패 시 Login failed 알림 창이 발생하고 따로 리디렉션은 이루어지지 않아야 할 것 같은데, 이 부분은 어떻게 해결할 수 있을까요..?

백엔드 측에서는 401 에러를 알맞게 내려주고 있는데 메인 페이지로 리디렉션되는 것은 프론트 코드의 문제가 아닐까 싶어요🤔

user-scripts.js 파일의login 메서드 > fetch('/login' ….) 부분 코드를 아래의 코드로 바꾸면 의도하신 대로 잘 돌아가네요!

fetch('/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ email, password })
  })
  .then(response => {
    if (!response.ok) {   // 수정
      alert('Login failed');
      throw new Error('Login failed');
    }
  })
  .then(() => {
    updateUIBasedOnLogin();
    window.location.href = '/';
  })
  .catch(error => {
    console.error('Error during login:', error);
  });
  1. 관리자만 관리자 endpoint에 접근할 수 있도록 @AdminRoute 어노테이션을 통해 적용해보았는데, 지금 방식의 경우 각 메소드 / 클래스마다 일일히 어노테이션을 붙여주어야만 작동하도록 구현되어있습니다. 따라서 해당 어노테이션을 까먹고 적용하지 않는 경우 문제가 될 수 있을 것 같은데, 이러한 경우를 방지하려면 어떠한 방식으로 접근하는게 좋을까요?

현재 @AdminRoute 어노테이션 기반으로 관리자 인가를 처리하고 있는데, 해당 방식은 유연하게 사용할 수 있지만 말씀해주신 대로 누락될 위험이 있을 것 같아요🤔

이러한 문제를 방지하기 위해

  1. 관리자 API를 /admin/**와 같이 경로 기준으로 구분하고, 해당 경로의 접근은 기본적으로 차단하되 관리자 권한을 가진 사용자만 허용하는 Default Deny 방식으로 설계하는 방법이 있을 것 같아요!
  2. Spring Security와 같은 보안 프레임워크를 사용하면, 인가 규칙을 중앙에서 관리할 수 있을 것 같습니다! 추후 학습해보시면 좋을 것 같아요

Choose a reason for hiding this comment

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

현재 save 메서드가 Member 엔티티를 반환하고 있는데,
Service에서 생성된 id를 즉시 활용하지 않는다면, 반환 타입을 void로 두고 해당 메서드에서는 저장만 하도록 할 수 있을 것 같아요!

connection.prepareStatement와 KeyHolder를 사용한 이유는 생성된 id를 바로 사용하기 위함인가요?!

만약 엔티티를 반환하지 않는 구조라면, 아래와 같이 단순한 형태로 수정할 수 있을 것 같아요.

public void save(Member member) {
    jdbcTemplate.update(
        "INSERT INTO member(name, email, password, role) VALUES (?, ?, ?, ?)",
        member.getName(),
        member.getEmail(),
        member.getPassword(),
        member.getRole()
    );
}

Copy link
Author

Choose a reason for hiding this comment

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

id를 즉시 사용하지 않는다면 해윤님께서 제안해주신 대로 더 간단한 방식으로 수정하는게 더 적절하다고 생각합니다! 다만 현재 구현의 경우 생성 시 response에 생성된 엔티티의 id를 Location 헤더에 넘겨주도록 적용되어 있어 id가 필요한 상황이라고 생각합니다..

물론 이 부분의 경우 제가 작성한 부분이 아닌 boilerplate 코드 자체에서 이미 구현되어 있던 부분인지라 만약 필요하지 않다면 수정하는 방향이 더 적절하다고 생각되는데, 해윤님의 생각이 궁금합니다!

Copy link

Choose a reason for hiding this comment

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

앗 지난 미션에서 작성된 부분이 아닌, 아예 새로 구현되어있던 부분이군요 ㅎ...
그렇다면 현재의 방법을 유지해도 좋을 것 같습니다!
(다만 개인적으로는 id를 넘겨줄 필요성을 못느꼈어요...🤔)

Copy link
Author

Choose a reason for hiding this comment

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

물론 저도 id를 넘겨줄 필요성을 못느끼긴 했습니다 ㅎㅎ.. 그치만 기존 구현에 남아있던 부분인지라 일단 남겨두도록 하겠습니다..!

Comment on lines 18 to 22
public MemberResponse createMember(MemberRequest memberRequest) {
Member member = memberDao.save(new Member(memberRequest.name(), memberRequest.email(), memberRequest.password(), "USER"));

return new MemberResponse(member.getId(), member.getName(), member.getEmail());
}
Copy link

@haeyoon1 haeyoon1 Dec 28, 2025

Choose a reason for hiding this comment

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

이건 개인적인 제 코드 취향인데 이런 방법도 있구나 정도로 보고 넘어가셔도 될 것 같아요~

위에서 남겼던 리뷰처럼 dao의 return 타입이 void라면 이렇게 저장만 하고, return 할때 MemberResponse를 조립하는 코드는 dto 내부에서 정팩매로 하는 방법도 있습니다!

    public MemberResponse createMember(MemberRequest memberRequest) {
        Member member = new Member(memberRequest.name(), memberRequest.email(), memberRequest.password(), "USER")
        memberDao.save(member);

        return MemberResponse.from(member);
    }

Copy link
Author

Choose a reason for hiding this comment

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

언급해주신 방법의 경우 파라미터가 많아질 경우 가독성을 해칠 수 있는 부분에 대한 좋은 해답인 것 같습니다!
가독성 측면을 벗어나 구현 방식의 차이를 보았을 때, 기존 구현의 경우 dao에서 리턴한 값을 가지고 MemberResponse를 생성하여 리턴해주는 방식이고, 해윤님이 제안해주신 방식의 경우 Member 객체를 만든 뒤, dao에 저장한 후, dao에서 리턴된 값이 아니라 dao에 넣어준 값(member)를 MemberResponse로 포장하여 리턴해주고 있는 모습을 확인할 수 있었습니다.

여기서 한가지 궁금한 점은 해당 방식으로 리턴한다면 member 객체 주입 시점에서는 존재하지 않는 값들(ex. id)의 경우 리턴해줄 수 없다고 생각하는데, 이러한 경우에는 어떤 방식으로 해결하는게 좋을지 궁금합니다!

Copy link

@haeyoon1 haeyoon1 Jan 1, 2026

Choose a reason for hiding this comment

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

member 객체 주입 시점에서는 존재하지 않는 값들(ex. id)의 경우 리턴해줄 수 없다고 생각하는데, 이러한 경우에는 어떤 방식으로 해결하는게 좋을지 궁금합니다!

이 부분은 저도 생각치못했네요😮 그런 경우라면 id만 반환해주는 방법으로도 구현할 수 있을 것 같아요~ 혹시 수한님이 생각하시는 대안이 추가로 있다면 공유해주세요!

Long memberId = memberDao.save(member);
return new MemberResponse(memberId, member.getName(), member.getEmail());

Copy link
Author

Choose a reason for hiding this comment

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

제가 생각한 방법은 초기 구현처럼 dao에서 리턴 값으로 Member 객체 자체를 리턴하여 필요한 값만 취사선택하는 방식이 떠오르긴 해요. 이 방법에서 오는 다른 trade-off가 크지 않다면 이러한 방법으로 적용하는 것도 나쁘지 않아보이는데 어떻게 생각하시나요?

Copy link

Choose a reason for hiding this comment

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

현재의 방법도 좋은 것 같습니다! 유연하게 사용할 수 있으니까요~
정답은 없으니 원하시는 방법으로 작성하셔도 될 것 같아요👍

}

public String createToken(LoginRequest request) {
Member member = memberService.authenticate(request.email(), request.password());

Choose a reason for hiding this comment

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

authenticate 메서드는 왜 AuthService내부에서 직접 작성하지 않고, MemberService에 작성 후 호출하는 방법으로 구현하셨나요?

Copy link
Author

Choose a reason for hiding this comment

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

음 저 부분이 저도 구현하면서 되게 애매하다고 생각했던 부분인데요, 해당 메소드의 이름이나 기능 상으로 보았을 때는 AuthService에 위치하는게 더 적절하다고 생각합니다. 다만 내부 로직에서 MemberDao를 호출하는 부분이 있어 다른 메소드들과의 일관성을 위해 MemberService에 위치하도록 적용해보았는데, 혹시 더 좋은 의견이 있으신지 궁금합니다!

Copy link

Choose a reason for hiding this comment

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

authenticate는 유효한 회원인지 찾는 역할이고, createToken에서는 authenticate를 통해 반환된 Member를 통해 토큰을 생성하는 역할을 담당하고 있는 것 같아요.

내부 로직에서 MemberDao를 호출하는 부분이 있어 다른 메소드들과의 일관성을 위해 MemberService에 위치하도록 적용

이 표현은 비슷한 말이긴하지만,
회원 검증하는 도메인 책임을 MemberService가 가지도록 분리했다 라고 표현하면 더 좋을 것 같아요~

결론적으로는 현재의 구조는 적절하게 설계된 것 같습니다!

Comment on lines 41 to 44
Cookie cookie = new Cookie("token", token);
cookie.setHttpOnly(true);
cookie.setPath("/");
// cookie.setMaxAge(60 * 60); // 1h

Choose a reason for hiding this comment

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

쿠키 생성 로직이 controller에 있는데 분리할 수 있을 것 같아요

Copy link
Author

Choose a reason for hiding this comment

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

확인했습니다! 별도의 Component를 통해 관리할 수 있도록 수정했습니다~!

4c3f2f1 커밋에 반영했습니다.

Choose a reason for hiding this comment

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

현재 controller create 메서드 내부에서

  1. 직접 입력값 검증을 하고
  2. 엔티티를 요청 body로 직접 받고있네요!

이 부분에 대한 수한님의 의견이 궁금합니다!

Copy link
Author

@chemistryx chemistryx Dec 30, 2025

Choose a reason for hiding this comment

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

입력값 검증의 경우 단순 요청 자체의 유효성을 검증한다는 측면에서는 컨트롤러에서 담당해도 문제가 없다고 생각합니다! 하지만 다른 코멘트(#195 (comment)) 에서 말씀해주신 것처럼 검증 로직이 비대해진다면 서비스 레이어 쪽으로 책임을 분산시키는 것도 고려해볼만 하다고 생각합니다.

엔티티를 요청 body로 직접 받는 경우는 사실 지양해야할 패턴이라고 생각합니다! 지금 방식의 경우 HTTP 요청 모델과 도메인 모델이 매우 강하게 결합되어 있어 추후에 엔티티 필드 변경 시 영향 범위가 매우 커진다고 생각합니다.

해당 부분의 경우 boilerplate 코드를 그대로 사용하다보니 미처 제가 파악하지 못한 부분이었는데, 수정 진행하도록 하겠습니다!

63e4768 커밋에 반영했습니다.

Comment on lines +21 to +29
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberArgumentResolver);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminRouteInterceptor);
}

Choose a reason for hiding this comment

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

스터디에서 나온 HandlerMethodArgumentResolver와 HandlerInterceptor를 잘 활용해주셨네요👏👏👏

인증(LoginMemberArgumentResolver)과 인가(AdminRouteInterceptor)를 각각 다른 컴포넌트로 분리해 주셨는데, 어떤 기준으로 설계하신 건지 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

인증과 인가는 서로 책임과 그 시점이 다르다고 생각합니다!
인증의 경우 컨트롤러 메소드 내부에서 로그인 된 사용자의 정보가 필요하기 때문에 파라미터 주입에 적합한 HandlerMethodArgumentResolver를 사용하였고, 인가는 요청 자체를 처리할 수 있는지 여부를 컨트롤러 진입 전에 판단해야 하기 떄문에 HandlerInterceptor를 도입하여 처리하였습니다..!


return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation);
// request body에서 찾고 없으면 member
String name = Optional.ofNullable(request.name()).orElseGet(() -> member != null ? member.getName() : null);

Choose a reason for hiding this comment

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

요 로직은 조금 더 가독성있게 바꿀 수 있을 것 같아요!
또한 이 부분은 service쪽으로 옮겨도 될 것 같다는 생각도 드는데 어떻게 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

말씀해주신대로 가독성이 좀 떨어짐 + 컨트롤러 내부 로직이 좀 무겁다고 판단하여 검증 로직 일부를 서비스 레이어로 이관하고 해당 과정에서 가독성 문제 또한 수정했습니다!

6eea9c2 커밋에 반영했습니다.

// request body에서 찾고 없으면 member
String name = Optional.ofNullable(request.name()).orElseGet(() -> member != null ? member.getName() : null);

if (name == null) return ResponseEntity.badRequest().build();

Choose a reason for hiding this comment

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

이 부분을 400에러로 처리하신 이유가 궁금합니다! 401로도 해석할 수 있을 것 같아서 리뷰 남겼어요!

Copy link
Author

@chemistryx chemistryx Dec 30, 2025

Choose a reason for hiding this comment

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

해당 부분의 경우 먼저 request에서 name을 찾고, 만약 없다면 member에서 name을 찾는 로직인데, 이 두 케이스 모두 실패할 경우 400을 반환합니다. 사실 일반적인 케이스에서는 name이 null이 될 상황이 없는지라 일종의 방어 로직으로 작성해 둔 코드라고 볼 수 있어요.

관리자가 제 3자의 예약을 등록하는 케이스에서 모종의 이유로 name이 null이 되어서 해당 부분이 불린다고 했을 때, 401을 던지면 해당 오류가 payload에 문제가 있어서 등록에 실패한 것인지, 관리자 권한 문제로 인해 등록에 실패한 것인지 알 수 없을 것 같아 해당 부분은 백엔드 관점에서 400으로 처리하는게 더 낫다고 판단했습니다!

@chemistryx
Copy link
Author

정말 상세한 리뷰 감사합니다 ㅎㅎ.. 말씀해주신 입력값 검증과 save 반환형의 경우 관련 코멘트 1, 관련 코멘트 2 에 제 생각을 달아두었는데 한번 확인 부탁드립니다!

아래는 질문 사항에 대한 답변입니다!

  1. 프로젝트 구조를 대표적으로 계층형(현재 작성해주신 구조) vs 도메인형 이렇게 나눌 수 있을 것 같은데, 계층형으로 작성하신 이유가 궁금합니다!

이 부분의 경우 저도 고민을 했던 부분인데, 기존 boilerplate의 경우 도메인형으로 구성되어 있었습니다. 하지만 현재 프로젝트의 경우 규모가 그리 크지 않고 요청 처리 흐름?이 비교적 단순한 편으로 보여 각각의 책임을 명확히 나누는 계층형 구조가 이해나 유지보수 측면에서 더 적합하다고 판단했습니다.
도메인형의 경우 도메인이 커지고 복잡해질수록 장점이 있다고 생각하여 추후에 규모가 확장된다면 충분히 고려해볼만 하다고 생각합니다..!

  1. 또한 전반적으로 에러 상황에 대한 응답 포맷이 상태 코드로만 내려가고 있는데, 공통 에러 응답 포맷을 정의하면 더 일관된 에러 처리를 할 수 있을 것 같아요!

이건 ErrorResponse를 만들어서 해당 response 내 상태 코드와 메시지를 내려보낼 수 있도록 구성해보았는데, 혹시 의도하신게 맞을까요..? 일단 b9a6096 커밋에 반영해두었습니다!

추가로 @AdminRoute 어노테이션 기반의 문제점에 대한 해결 방법의 경우 Spring Security를 통해 적용해보는 방법이 추후에 응용하는데 있어서도 더 도움이 될 것 같아 적용해보려고 하는데, 아무래도 처음 접하다보니 시간이 좀 소요될 것 같습니다. 그래서 이번 리뷰 반영 과정에서는 완료하지 못할 것 같은데, 혹시 이번 미션 과정에서 천천히 진행해보아도 될까요?

이 외에도 리뷰 코멘트 달아주신 부분에 대해서는 확인 후 반영 및 코멘트 달아놓았는데 천천히 확인 부탁드립니다! 🙏🙏

Copy link

@haeyoon1 haeyoon1 left a comment

Choose a reason for hiding this comment

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

꼼꼼히 리뷰 잘 반영해주셨네요! 수고하셨어요

하지만 현재 프로젝트의 경우 규모가 그리 크지 않고 요청 처리 흐름?이 비교적 단순한 편으로 보여 각각의 책임을 명확히 나누는 계층형 구조가 이해나 유지보수 측면에서 더 적합하다고 판단했습니다.
도메인형의 경우 도메인이 커지고 복잡해질수록 장점이 있다고 생각하여 추후에 규모가 확장된다면 충분히 고려해볼만 하다고 생각합니다..!

👍 이미 잘 알고 계시지만 정리할겸, 고은님 리뷰에서 발견한 인데 한번 읽어보셔도 좋을 것 같아요~

이건 ErrorResponse를 만들어서 해당 response 내 상태 코드와 메시지를 내려보낼 수 있도록 구성해보았는데, 혹시 의도하신게 맞을까요..?

맞아요! 잘 적용해주셨네요

추가로 @AdminRoute 어노테이션 기반의 문제점에 대한 해결 방법의 경우 Spring Security를 통해 적용해보는 방법이 추후에 응용하는데 있어서도 더 도움이 될 것 같아 적용해보려고 하는데, 아무래도 처음 접하다보니 시간이 좀 소요될 것 같습니다. 그래서 이번 리뷰 반영 과정에서는 완료하지 못할 것 같은데, 혹시 이번 미션 과정에서 천천히 진행해보아도 될까요?

네 천천히 진행해주셔도 됩니다~

추가로 이번 미션에서 다룰 부분은 아니지만
image

현재는 DB에 회원 정보(비밀번호 등)가 그대로 저장되고 있는데요, 이는 보안상 위험할 수 있어요. 그래서 암호화 해 저장하는 등의 방법도 찾아보시면 좋을 것 같습니다!

새해 복 많이 받으세요🧧🧧🧧

#spring.jpa.defer-datasource-initialization=true

#roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=
roomescape.auth.jwt.secret=TbS+vo0mJ3SHXcryNYVaUmy0LHRDJU21ca5x02Ga4K6X2BPFFrkox+hYb2IC6n8yf47cR+expaFMmlAo4fB6Iw==
Copy link

Choose a reason for hiding this comment

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

public class JwtTokenProvider {
    @Value("${roomescape.auth.jwt.secret}")
    private String secretKey;

여기서 secretKey 정보를 잘 숨겨주셨는데요, 프로젝트시에는 properties에서도 해당 값이 외부에 노출되지 않는 것이 좋은데요

  1. 왤까요!
  2. 어떻게 숨길 수 있을까요?

(해당 미션에서는 그대로 두셔도 돼요!)

Copy link
Author

Choose a reason for hiding this comment

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

아마 제가 생각하기론 secretKey를 노출하게 되면 제가 jwt token을 생성한 방식과 동일하게 외부 공격자가 token을 생성할 수 있는 문제가 발생할 것 같아요! 그렇기 때문에 git상에 업로드가 되지 않도록 .gitignore파일에 추가하는 방식으로 개선이 필요할 것 같습니다..!

Copy link

@haeyoon1 haeyoon1 Jan 3, 2026

Choose a reason for hiding this comment

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

맞아요! 혹은 Github Actions에서 secret으로 관리하는 방법도 많이 사용됩니다!
무작정 .gitignore에 넣으면 팀원들끼리도 공유가 어려우니까요

블로그에 나와있듯 간단하게 설정할 수 있으니 프로젝트에서 적용해보시면 좋을 것 같아요~

.split(";")[0]
.split("=")[1];
}
}
Copy link

Choose a reason for hiding this comment

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

EOF!

@chemistryx
Copy link
Author

마지막까지 꼼꼼하게 답변 달아주셔서 감사합니다! 공유해주신 프로젝트 패키지 구조에 관한 인사이트 또한 많은 도움이 되었습니다..! 추가로 말씀해주신 비밀번호 평문 저장 같은 경우에도 해싱과 같은 기법을 통해 추후에 개선해보도록 하겠습니다~!

해윤님도 새해 복 많이 받으세요!! 🧧🎊🧧🎊

@boorownie boorownie merged commit 9ae00c2 into next-step:chemistryx Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants