프로젝트 - 2주차(AOP ,CORS)

YoungSun·2026년 1월 11일

2주차 느낀점

나 정말 뭘 해야 되지...?
모르겠다.

그래도 일단 공부하려고 컴퓨터를 키면
그때부터 유튜브와의 전쟁이 시작된다.
영상 하나 보고
관련 영상 하나 더 보고
그렇게 계속 보고…

정작 머릿속에는 아무것도 남지 않는다.
그 상태로 하루가 끝난다.
돌이켜보니 의지의 문제가 아닌것 같다
무엇을 해야 하는지가 너무 추상적이었다.
AOP 리팩토링 하기 CORS 개념 적용하기 등등

목표를 읽으면 바로 무언가를 해야될지 떠오르지 않는다.
시작 지점이 보이지 않기 때문에 어렵다.

마감이니까 임박해서 허겁지겁 WIL을 쓰고
코드를 추가하는 이유도 사실은 쉬운데
하기 싫고 어렵다고 생각해서 그렇다.

그러니까 앞으로 할 일들은
구체적으로, 쉽게 만들기 !

  1. CORS
  2. AOP

1. CORS

프론트 API 요청을 보냈는데 이상한 상황을 겪었다.

  • 서버 로그 : 정상
  • Postman 테스트 : 정상
  • 브라우저 : 요청 실패

크롬 브라우저 콘솔에는 빨간색 에러 메시지가 찍혀있다.

CORS!!

처음엔 프론트 엔드가 요청을 잘못 보낸줄 알았다.

하지만 문제는 브라우저 문제였고

백엔드에서 처리 가능한 문제였다

CORS는 흔히 보안 정책처럼 설명된다

정확하게는 브라우저가 요청을 처리하는 규칙 이다

핵심을 정리하면 이렇다.

  • 서버는 요청을 받고 응답까지 이미 보낸 상태
  • 브라우저가 응답을 버릴지 말지 판단
  • 기준은 응답 헤더에 포함된 CORS 관련 정보

이걸 이해하지 못하면 계속 서버 코드만 뒤진다.

왜 Postman에서는 되는데 브라우저에서는 안 될까?

CORS는 브라우저에만 적용되는 규칙이다.

Postman은 브라우저가 아니기 때문에

CORS 검사를 하지 않는다.

Preflight 요청은 허락을 구하는 과정이다

브라우저는 어떤 요청을 보내기 전에 먼저 묻는다.

“이 요청 보내도 괜찮아?”

이게 Preflight 요청이다.

흐름을 순서대로 정리하면:

  1. 브라우저가 OPTIONS 요청을 먼저 보낸다
  2. 서버가 허용 정책을 응답 헤더로 알려준다
  3. 브라우저가 그 내용을 보고 판단한다
  4. 허용되면 실제 요청을 보낸다

중요한 점은 이것이다.

서버는 차단하지 않는다.
판단자는 항상 브라우저다.

서버는 이런 요청은 허용해 라고 알려줄 뿐...

Spring AOP로 로그인 체크를 분리하면서 겪은 시행착오 정리

로그인 체크는 간단하게 보였다.

세션 하나만 꺼내서 값만 확인하면 끝이다.

문제는 USER / ADMIN 으로 갈라지면서 시작됐다

1주차 코드의 실제 문제점

1-1. RequestContextHolder를 무조건 신뢰함

RequestContextHolder.currentRequestAttributes()
  • 웹 요청이 아닐 경우 바로 예외
  • 스케줄러, 테스트, 비동기 작업에서 깨짐
    • 작업이 깨질때 이게 로그에서 보면 비동기 때문인지 스케줄러 때문인지 알 수가 없음
    • 디버깅 비용 증가

간단하게 RequestContextHolder는
HTTP 요청을 처리 중이라는 전제 하에 요청 내용을 가져온다.
아니면 그냥 NPE 던짐 → 나 : ??? 넌 어디서 왔니


1-2. 세션 구조가 AOP에 침투함

getLoginMemberId()
getLoginAdminId()

AOP가 세션 내부 구조를 알고 있다는 건 설계적으로 좋지 않다.

  • 역할이 늘면? → 역할에 맞춰서 추가해되야됨

  • API 타입이 늘면?

    AOP가 계속 수정된다.


1-3. 문자열 기반 분기

초기 구현에서는 다음과 같이 분기하고 있었다.

switch (loginCheck.type().toString())

당시에는 이 코드가 문제라고 생각하지 않았다.

USER, ADMIN 두 값만 비교하면 되니 충분해 보였다.

이 부분이 문제라는 걸 인지하게 된 건,

enum 타입에 대한 설명을 들은 이후였다.

@LoginChecktype은 단순한 문자열이 아니라

의미를 가진 타입이라는 점을 그때 처음 제대로 이해했다.

enum을 사용한다는 건,

  • 허용 가능한 값의 범위를 타입으로 제한하고
  • 잘못된 값은 아예 컴파일 단계에서 차단하며
  • 분기 누락이나 변경을 컴파일러가 추적해준다는 의미였다.

하지만 이를 String으로 변환해 비교하는 순간,

이런 장점들은 전부 사라진다.

즉, 이 코드는 동작은 하지만

타입이 제공하던 보호를 스스로 포기한 상태였다.

이 점을 이해한 뒤에는,

분기 기준을 굳이 문자열로 바꿀 이유가 없다는 게 분명해졌다.

그래서 이후 코드에서는 enum 자체를 기준으로 분기하도록 수정했다.

switch (loginCheck.type()) {
case USER -> ...
case ADMIN -> ...
}

이렇게 변경하면서,

로그인 정책의 분기 기준은 다시

컴파일 타임에 검증 가능한 형태로 돌아왔다.

간단하게 정리하면

나만의 커스텀 단어 오타 채점기


2. 구조를 다시 잡은 두 번째 버전

그래서 코드를 전면 수정했다.

@Aspect
@Component
publicclassLoginCheckAspect {

@Around("@annotation(loginCheck)")
public ObjectcheckLogin(
            ProceedingJoinPoint pjp,
            LoginCheck loginCheck
    )throws Throwable {

// 1. 웹 요청인지 확인
ServletRequestAttributesattrs=
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

if (attrs ==null) {
thrownewIllegalStateException("No request context");
        }

// 2. request 획득
HttpServletRequestrequest= attrs.getRequest();

// 3. 세션 획득 (없으면 null)
HttpSessionsession= request.getSession(false);

if (session ==null) {
thrownewHttpStatusCodeException(
                HttpStatus.UNAUTHORIZED,"No session") {};
        }

// 4. 타입별 로그인 정보 조회
        String userId;
switch (loginCheck.type()) {
case USER -> userId = SessionUtil.getLoginUserId(session);
case ADMIN -> userId = SessionUtil.getLoginAdminId(session);
default ->thrownewIllegalStateException("Unknown type");
        }

if (userId ==null) {
thrownewHttpStatusCodeException(
                HttpStatus.UNAUTHORIZED,"NO_LOGIN") {};
        }

return pjp.proceed();
    }
}

2-1. 이 구조에서 달라진 핵심 포인트

RequestContextHolder는 “항상 있다”는 가정을 버렸다

  • 웹 요청이 아닐 경우 즉시 차단
  • 실패 지점을 명확히 함

어노테이션 값은 자동으로 바인딩된다

@Around("@annotation(loginCheck)")
public ObjectcheckLogin(..., LoginCheck loginCheck)

loginCheck는:

  • 리플렉션(?)으로 직접 꺼낸 게 아니다
  • Spring AOP가 자동으로 주입해준다

리플랙션이 뭐지?


3. 그래도 아직 남은 문제

솔직히 말하면 이 구조도 완벽하지 않다.

getLoginUserId()
getLoginAdminId()

여전히:

  • 역할이 세션 구조에 묶여 있고
  • OAuth나 JWT로 가면 전부 버려야 한다

더 나은 방향은?

  • 세션에는 User 객체 하나
  • role은 속성으로 관리
  • AOP는 role만 비교

이렇게 가야 한다.


정리

  • 인증은 단순히 세션이나 토큰 하나로 끝나는 문제가 아니었다.
  • 세션 기반 인증을 이해했다고 생각했지만 그 위에 JWT가 있고 그 위에 OAuth가 얹히며 결국 Spring Security라는 프레임워크로 수렴된다.
  • 인증 로직은 기능이 아니라 구조의 문제라는 걸 알게 됐다.

다음 주에 공부할 범위가 자연스럽게 늘어났다.

https://www.youtube.com/watch?v=xsmKOo-sJ3c&list=PLJkjrxxiBSFALedMwcqDw_BPaJ3qqbWeB

다음주 할 일

Throwable,JWT 에 대한 블로그 딱 1개 찾아보기
Linking에 적용된 역 정규화 테이블 찾고 정규화 해보기
-> join 얼마나 차이나는지 AI한테물어보기
유효성 검사, 전역 예외처리 코드 추가하기

할 일 구체적으로 정하기

1개의 댓글

comment-user-thumbnail
2026년 1월 12일

CORS는 저도 백엔드 연결때마다 겪는 문제인 것 같아요
제가 했던 프로젝트에서는 여기서 막혀서 해결하는데 시간이 꽤 걸렸던 기억이 있는데 잘 해결하신 것 같네요!!

답글 달기