Skip to content

[9 주차] 조항준 / Chapter09. Spring Security - JWT, OAuth - #46

Open
cho-hj-dev wants to merge 6 commits into
UMC-AYU:mainfrom
cho-hj-dev:Aim-Chapter09
Open

[9 주차] 조항준 / Chapter09. Spring Security - JWT, OAuth#46
cho-hj-dev wants to merge 6 commits into
UMC-AYU:mainfrom
cho-hj-dev:Aim-Chapter09

Conversation

@cho-hj-dev

@cho-hj-dev cho-hj-dev commented May 25, 2026

Copy link
Copy Markdown
Contributor

🔗 Issue Number


📝 개요


🚀 주요 변경 사항


🖼️ 실행 결과 (Screenshots)

image

💬 고민 및 질문

아직 미션2를 진행하지 못했습니다. 다시 한 번 시도해볼 수 있도록 해보겠습니다. 그리고 이번주차 역시 최대한 워크북에 있는 내용을 따라갈 수 있도록 노력했습니다. 그 중 토큰이 만료되었거나(ExpiredJwtException), 잘못된 토큰 형식일 때 JwtAuthFilter에서 예외를 낚아채어 팀 내에서 정의한 공통 에러 포맷(COMMON401_1)으로 프론트엔드에 일관되게 내려주도록 설계했는데, 필터 단에서 터지는 예외를 GeneralExceptionAdvice 같은 전역 핸들러와 매끄럽게 연결하는 구조가 시큐리티 표준 예외 처리 규격에 잘 맞는지 확인 한 번 해주시면 감사하겠습니다.
그리고 파트장님이 지적해주신 부분 또한 빠르게 수정하겠습니다. 죄송합니다.

✅ 실습 체크리스트

  • 이론 학습을 완료 했나요?
  • 미션 요구사항을 이해했나요?
  • 미션을 완료 했나요?

⚙️ 환경 및 컨벤션 체크 (Final Check)

  • 디렉토리 구조 컨벤션을 지켰나요?
  • pr 제목을 컨벤션에 맞게 작성하였나요?
  • pr에 해당되는 이슈를 연결하였나요?
  • Assignees을 본인으로 설정했나요?
  • Reviewers을 설정 했나요?
  • 적절한 라벨을 설정하였나요?

@cho-hj-dev
cho-hj-dev requested review from a team and zldzldzz May 25, 2026 14:13
@cho-hj-dev cho-hj-dev self-assigned this May 25, 2026
@cho-hj-dev
cho-hj-dev requested review from hwahwahwan and soseongmin03 and removed request for a team May 25, 2026 14:14
Comment on lines +15 to +37
@Component
public class CustomAccessDenied implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException
) throws IOException, ServletException{

response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN); //403 Forbidden 설정

ObjectMapper objectMapper = new ObjectMapper();

ApiResponse<Object> errorResponse = ApiResponse.onFailure(
GeneralErrorCode.FORBIDDEN.getCode(),
GeneralErrorCode.FORBIDDEN.getMessage(),
null
);

response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

형석님과 같은 부분에서 드리는 리뷰인데 현재 요청이 들어올 때마다 objectmapper를 생성하고 있으신데 이렇게 생성하면 Jackson설정을 하게 된다면 이 핸들러에는 반영이 되지않습니다. 스프링 빈 주입방식을 사용하시는 것이 좋아보입니다.
응답 상태 코드 처리 부분에서는 현재 403_Forbidden을 직접 지정하시는데 GeneralErrorCode에 정의된 status를 기준으로 응답하도록 하면 공통 에러 코드 응답 체계와 맞을 것 같습니다.

Comment on lines +27 to +37
// 💡 원래 잘 작동하던 로컬 생성 방식으로 복구!
ObjectMapper objectMapper = new ObjectMapper();

ApiResponse<Object> errorResponse = ApiResponse.onFailure(
GeneralErrorCode.UNAUTHORIZED.getCode(),
GeneralErrorCode.UNAUTHORIZED.getMessage(),
null
);

response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

원래 잘 작동하던 방식으로 복구하셨다고 하셨는데 어떤 애로사항이 있었나요?

@zldzldzz

zldzldzz commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

토큰에 권한(role)을 넣지만 실제 권한은 항상 빈 값 (AuthMember.java)

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities(){
      return List.of();   // 항상 비어 있음
  }

JwtUtil은 member.getAuthorities()를 모아서 "role" 클레임에 저장하는 (JwtUtil.java), 이 메서드가 항상 빈 리스트라 토큰의 role도 늘 비어 있게 됩니다. 지금은 anyRequest().authenticated()(로그인만 했으면 통과)라 문제없지만 나중에 hasRole("ADMIN") 같은 **인가(권한 구분)**를 하려 하면 전부 막힙니다.

해결 방법
Member에 역할 정보가 있다면 그걸 반환하도록 바꿉니다.

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities(){
      // 우선 기본 USER 권한이라도 부여 (가장 간단한 형태)
      return List.of(new SimpleGrantedAuthority("ROLE_USER"));
  }

같은 토큰을 두 번 파싱(JwtAuthFilter.java)

 if(jwtUtil.isValid(token)){          // 1번째 파싱 (검증)
      String email = jwtUtil.getEmail(token);  // 2번째 파싱 (이메일 추출)

isValid도 내부에서 getClaims(token)로 토큰을 풀고, getEmail도 또 풉니다. 즉 한 요청마다 암호 검증을 2번 합니다. 기능엔 문제없지만 불필요한 중복입니다.

해결 방법
검증과 동시에 이메일을 한 번에 꺼내면 파싱 1회로 끝납니다.

public String getEmailIfValid(String token){
      try {
          return getClaims(token).getPayload().getSubject(); // 검증+추출 동시
      } catch (JwtException e){
          return null;
      }
  }

  // 필터에서는
  String email = jwtUtil.getEmailIfValid(token);
  if (email != null) {
      UserDetails user = customUserDetailsService.loadUserByUsername(email);
      ...
  }

에러코드 복사/붙여넣기 실수(GeneralErrorCode.java)

 NOT_FOUND(HttpStatus.NOT_FOUND,
            "COMMON401_1",        // 404인데 코드 문자열이 401
            "해당 리소스를 찾을 수 없습니다."),
  - 원인: HTTP 상태는 NOT_FOUND(404)인데 코드 문자열만 COMMON401_1로 되어 있습니다. UNAUTHORIZED(line 16)와 코드가 겹쳐서, 프론트가 코드값으로 에러를 구분할 때 401인지 404인지 헷갈리게 됩니다.
  - 해결 예시:
  NOT_FOUND(HttpStatus.NOT_FOUND,
            "COMMON404_1",        //  수정
            "해당 리소스를 찾을 수 없습니다."),

// 해결 후
NOT_FOUND(HttpStatus.NOT_FOUND,
            "COMMON404_1",
            "해당 리소스를 찾을 수 없습니다."),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chapter09_Spring Security - JWT, OAuth

3 participants