[데브코스] Spring Boot 인증·인가(Auth) (33강) - 컨트롤러 중복 인증 로직을 Rq(RequestScope)로 리팩토링하기

zuno·2026년 1월 13일

이번 강의(33강)는 한 줄로 요약하면 이거다.

👉 컨트롤러마다 반복되던 “Authorization 헤더 처리 + apiKey로 Member 조회” 로직을 Rq.getActor()로 옮겨서, 컨트롤러를 깔끔하게 만든다.


1) 리팩토링 전: 컨트롤러에 중복 로직이 계속 있었음

글/댓글 작성·수정·삭제 컨트롤러마다 아래 로직이 거의 똑같이 반복됐다.

  • Authorization 헤더 꺼내기
  • "Bearer " 제거
  • apiKey 추출
  • apiKey로 Member 조회
  • 인증 실패면 예외 처리

예를 들면 (기존 방식 느낌):

String apiKey = authorization.replace("Bearer ", "");
Member actor = memberService.findByApiKey(apiKey)
    .orElseThrow(() -> new ServiceException("401-1", "존재하지 않는 apiKey 입니다."));

이게 컨트롤러마다 있으면 뭐가 문제냐?

  • 코드가 지저분해짐 (핵심 비즈니스 로직이 묻힘)
  • 수정할 때 전부 찾아서 고쳐야 함 (실수 발생 확률 ↑)
  • 인증 로직이 분산됨 (일관성 깨질 수 있음)

그래서 강의에서는 “이 중복을 한 곳으로 모으자” 라고 방향을 잡는다.


2) 리팩토링 후 핵심: 컨트롤러는 rq.getActor()만 호출

컨트롤러에서 인증 관련 로직을 직접 하지 않고 이렇게 바뀐다.

Member actor = rq.getActor();

즉,

  • 컨트롤러는 “요청 처리”에 집중
  • 인증/헤더 파싱/회원 조회는 Rq가 담당

3) 33강에서 구현된 Rq.getActor() 코드 흐름

이번 강의의 핵심 구현은 Rq.getActor() 안에 들어간다.

@Component
@RequestScope
@RequiredArgsConstructor
public class Rq {
    private final MemberService memberService;
    private final HttpServletRequest req;

    public Member getActor() {
        String headerAuthorization = req.getHeader("Authorization");

        if (headerAuthorization == null || headerAuthorization.isBlank())
            throw new ServiceException("401-1", "Authorization 헤더가 존재하지 않습니다.");

        if (!headerAuthorization.startsWith("Bearer "))
            throw new ServiceException("401-2", "Authorization 헤더가 Bearer 형식이 아닙니다.");

        String apiKey = headerAuthorization.substring("Bearer ".length()).trim();

        Member member = memberService
                .findByApiKey(apiKey)
                .orElseThrow(() -> new ServiceException("401-3", "API 키가 유효하지 않습니다."));

        return member;
    }
}

이걸 “초보자 흐름”으로 풀면 이렇게 된다.

(1) Authorization 헤더 꺼냄

String headerAuthorization = req.getHeader("Authorization");

(2) 헤더가 없거나 비어있으면 “아예 인증 정보가 없음”

if (headerAuthorization == null || headerAuthorization.isBlank())
    throw new ServiceException("401-1", "Authorization 헤더가 존재하지 않습니다.");

여기서 중요한 포인트:
헤더 자체가 없으면 @NotBlank 검증까지 도달하기 전에 터질 수 있음
(왜냐면 컨트롤러 파라미터 바인딩 단계에서 이미 문제가 생기기 때문)

(3) Bearer 형식인지 검사

if (!headerAuthorization.startsWith("Bearer "))
    throw new ServiceException("401-2", "Authorization 헤더가 Bearer 형식이 아닙니다.");

(4) "Bearer " 잘라서 apiKey만 뽑기

String apiKey = headerAuthorization.substring("Bearer ".length()).trim();
  • substring("Bearer ".length()) : 앞의 "Bearer " 제거
  • trim() : 혹시 공백 들어가도 정리

(5) apiKey로 회원 조회 → 없으면 인증 실패

Member member = memberService.findByApiKey(apiKey)
    .orElseThrow(() -> new ServiceException("401-3", "API 키가 유효하지 않습니다."));

4) 컨트롤러는 어떻게 바뀌었나? (33강 리팩토링 포인트)

변경 전(느낌)

  • @RequestHeader("Authorization") String authorization를 받고
  • 그 자리에서 헤더 파싱하고
  • 멤버 조회까지 함

변경 후(33강 결과)

  • 컨트롤러에서 Authorization 파라미터를 “굳이” 직접 받을 필요가 줄어듦
  • 대신 rq.getActor()로 통일

예시(수정된 형태 느낌):

public RsData<PostDto> write(@Valid @RequestBody PostWriteReqBody reqBody) {
    Member actor = rq.getActor();
    Post post = postService.write(actor, reqBody.title, reqBody.content);
    ...
}

즉, 컨트롤러에서 인증 로직이 사라지고 “내가 지금 해야 할 일”만 남는 구조가 된다.


5) “요청이 100개 오면 컨트롤러 필드 값 덮어써지는 거 아님?”에 대한 오해 정리

이 부분은 헷갈리기 쉬워서 정리해둘게.

✅ 컨트롤러는 보통 싱글톤(1개) 맞다

근데 컨트롤러가 ‘상태값을 필드에 저장’하지 않으면 덮어써질 게 없다.

  • 컨트롤러 메서드는 요청마다 “호출”될 뿐
  • 요청 데이터는 대부분 “메서드 파라미터/지역변수”로 처리됨
  • 지역변수는 각 요청(각 스레드)마다 따로 잡힘

그래서 보통 이런 코드는 안전하다:

public void something() {
    String x = "..."; // 지역변수
}

❌ 진짜 위험한 건 “전역/싱글톤 객체에 사용자 상태를 저장하는 것”

예를 들어 이런 식이면 큰일남:

// (절대 하면 안 되는 예시)
@Component
public class AuthStore {
    private Member currentActor; // 전역 상태
}

요청 A가 currentActor=user1 넣고,
요청 B가 currentActor=user2 덮으면,
요청 A가 다시 읽을 때 user2가 나올 수 있음 → 보안 사고.


6) 그럼 Rq는 new로 생성되는 게 아니야?

결론부터 말하면:

  • 내가 new Rq()를 직접 안 한다
  • 스프링이 요청 시작 시점에 Rq를 만들어서 관리한다

그리고 중요한 사실:

요청 100개면 Rq도 100개 생기는 게 맞다
근데 그 생성은 스프링 컨테이너가 한다.

또 하나 더 중요한 포인트:

✅ 한 “요청” 안에서 rq.getActor()를 2번 호출해도
Rq 객체가 2개 생기는 게 아니라, 그 요청에 해당하는 Rq 1개를 계속 쓴다.

(요청 단위 1개 유지)


8) 핵심 요약

  • 33강의 목표: 컨트롤러 중복 인증 로직 제거
  • 해결책: Rq.getActor()에 인증 로직을 모음
  • 컨트롤러는 Member actor = rq.getActor();만 쓰게 리팩토링
  • RequestScope는 요청마다 Rq 1개, 요청 끝나면 소멸

이제 컨트롤러 코드가 “인증 처리 코드”에 묻히지 않고,
진짜 하고 싶은 비즈니스 로직만 남는 형태로 정리됐다.

0개의 댓글