interceptor 구현

leejihyeon·2026년 5월 9일

코드 이해하기

1. build.gradle에 추가

// 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
//전체 코드

2. interceptor 상속받기

public class aminCheckInterceptor implements HandlerInterceptor {
  • 인터셉터를 만들고 싶으면 최소한 규칙(인터페이스) HandlerInterceptor를 지켜야 한다.
  • "내가 너희가 만든 인터셉터 규격(인터페이스)에 맞춰서 코드를 구현할게" 라고 선언하는 키워드가 implements이다.
  • public class [클래스명] implements HandlerInterceptor 구조

3. 로그인한 사용자(인증)확인하기 위해 이름(이메일, 고유 식별자) 꺼내기

// 1. 현재 로그인한 사용자 이름 꺼내기
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
	String currentusername = auth.getName();
  • getNameSecurityContextHolder메서드에 들어가면 있음
  • getName은 이메일 또는 고유의 식별자임

4. 요청 URI에서 username 추출

// 2. [요청 URI 추출] 사용자가 요청한 전체 주소 경로를 가져옵니다. (예: /api/user/1L/email)
String path = request.getRequestURI();

// 3. https://ko.wikipedia.org/wiki/%EB%94%94%EC%BD%94%EB%94%A9 한글 이름이나 특수문자가 주소창에 포함되어 깨지는 현상을 방지하기 위해 UTF-8 문자셋으로 디코딩합니다.
String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);

// 4. [경로 분석] 디코딩된 주소를 '/' 문자를 기준으로 조각냅니다.
// 예: "/api/user/1L/email" -> ["", "api", "user", "1L", "email"]
String[] parts = decodedPath.split("/");
        
// 5. [대상 유저 추출] 끝에서 두 번째 조각을 가져옵니다. (위 예시의 경우 뒤에서 두 번째인 "1L"이 추출됨)
String username = parts[parts.length - 2];

5. 관리자 권한 비교

💡 자바의 추상화(Abstraction) 한눈에 이해하기

자바에서 추상화(Abstraction)는 크게 두 가지 방식으로 구현됩니다.
내가 만드는 클래스의 목적에 따라 extendsimplements를 적절하게 선택해야 합니다.

       [ 추상화 (Abstraction) ]
                  │
 ┌────────────────┴────────────────┐
 ▼                                 ▼
추상 클래스 (Abstract Class)      인터페이스 (Interface)
 - 상속하여 확장할 때 사용 (is-a)     - 공통 규격/계약을 강제할 때 (has-a)
 - 키워드: extends                 - 키워드: implements
  • 인터페이스(Interface)는 100% 추상화 상태입니다.

    • HandlerInterceptor 내부에는 "이런 메서드들이 필요해~"라고 껍데기(메서드 이름과 리턴 타입)만 정의되어 있고, 실제 알맹이(로직)는 비어 있습니다. (이게 바로 추상화입니다.)
  • implements (구현):

    • 비어 있는 껍데기(추상화)를 지현님이 가져와서 { ... } 중괄호 안에 실제 동작하는 자바 코드를 채워 넣는 작업(구현)입니다.

쉽게 이해하는 비유 (스마트폰 충전기)

  • HandlerInterceptor (인터페이스/추상화):

    • 국가 기술 표준원이 정한 "C타입 충전기 규격"입니다. 실체가 없는 문서상의 규격(구멍 크기, 핀 개수)일 뿐입니다.
  • (구현 클래스):

  • 삼성이나 애플이 실제로 공장에서 만들어낸 "진짜 C타입 충전기 제품"입니다.

  • implements:

    • "우리는 국가 표준 C타입 규격을 준수하여(implements) 제품을 만들었습니다"라고 마크를 다는 것입니다.

그래야 스마트폰(스프링)에 꽂았을 때 에러 없이 충전이 되겠죠?

💡 한 줄 요약

  • 왜 implements 하나요? * 스프링이 제공하는 인터셉터 규격(HandlerInterceptor)에 내 클래스를 맞춰서 스프링이 인식할 수 있게 만들기 위함입니다.

  • implements가 추상화인가요? * 맞습니다! 알맹이가 없는 추상적인 규격(인터페이스)을 가져와서, 실제 동작하는 코드로 완성(구현)하는 도구가 바로 implements입니다.

💡 getName()이 왜 이름이 id가 아니라 name일까?

스프링 시큐리티가 상속받는 자바의 표준 보안 규격(java.security.Principal) 인터페이스를 보면, 사용자의 고유한 식별 정보를 가져오는 메서드가 하필 getName()으로 정의되어 있습니다.

public interface Principal {

    public boolean equals(Object another);

    /**
     * Returns a string representation of this principal.
     *
     * @return a string representation of this principal.
     */
    public String toString();

    /**
     * Returns a hashcode for this principal.
     *
     * @return a hashcode for this principal.
     */
    public int hashCode();

    /**
     * Returns the name of this principal.
     *
     * @return the name of this principal.
     */
    // 자바 표준 규격: 사용자를 유일하게 식별할 수 있는 이름을 반환해라!
    public String getName();
  • 자바 설계자가 "사용자를 식별하는 유일한 값"의 이름을 getId()가 아니라 getName()으로 지어둔 것이다.

  • 여기서 말하는 Name은 우리가 흔히 쓰는 본명(예: "홍길동")이 아니라, 시스템이 나를 중복 없이 유일하게 알아볼 수 있는 "식별자(Username / Email / Login ID)"를 의미한다.

실제 스프링 시큐리티에서의 흐름

  • 스프링 시큐리티에서 로그인(인증)이 성공하면 내부적으로 다음과 같은 일이 일어난다.
  1. 로그인 시도: 지현님이 email과 password로 로그인을 합니다.

  2. UserDetails 생성: 스프링 시큐리티는 인증된 사용자 정보를 UserDetails 객체에 담습니다. 이때 홍길동의 이메일(또는 로그인 ID)이 username 자리에 저장된다.

  3. SecurityContextHolder에 보관: 이 인증 정보가 Authentication이라는 객체로 감싸져서 컨텍스트에 저장된다.

  4. auth.getName() 호출 시:

    • 스프링 시큐리티는 Authentication 내부의 Principal 객체(실제로는 우리가 커스텀한 UserDetails 구현체)를 들여다 본다.

    • 그리고 그 안에 들어있는 username(즉, 로그인 ID나 이메일)을 꺼내서 반환해 준다.

💡 한 줄 요약

  • 실제 데이터: "test@email.com" 또는 "1L" (중복 없는 고유 ID)

  • 꺼내는 메서드: auth.getName() (규격상 이름이 getName일 뿐!)

만약 본명(진짜 이름)을 꺼내고 싶다면?

만약 로그인 ID가 아니라 데이터베이스에 저장된 진짜 사용자 이름(예: "홍길동")을 꺼내고 싶다면, auth.getName()을 쓰면 안 되고 아래처럼 Principal을 실제 객체로 다운캐스팅(형변환)해서 가져와야한다.

// 로그인할 때 커스텀한 UserDetailsImpl을 썼다고 가정했을 때
UserDetailsImpl userDetails = (UserDetailsImpl) auth.getPrincipal();

String loginId = userDetails.getUsername(); // 고유 ID (auth.getName()과 동일)
String realName = userDetails.getNickname(); // 데이터베이스에 저장된 진짜 이름/닉네임

0개의 댓글