Spring Security의 유연성과 복잡성

GreatCloud·2026년 3월 10일

백엔드

목록 보기
9/14

지난 포스트에서 도메인 모델의 순수성을 지키기 위해 PasswordEncoder를 파라미터로 넘기는 방법과 같은 '적정선'에 대해 고민을 해보았고 오늘 Spring Security는 왜 이렇게 복잡한 구조인지 한번 알아보려고 한다

발단

지금 진행중인 클라이밍 앱의 주요 핵심기능들의 개발을 진행하고 미뤄두던 인증 관련 Boilerplate Code를 개발하며 SpringSecurityUsernamePasswordAuthenticationToken 내부의 코드의 읽다 변수명들을 발견하고 문득 이런 의문이 생겼다.

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal; // << 여기
        this.credentials = credentials; // << 여기
        this.setAuthenticated(false);
    }

    public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal; // << 여기
        this.credentials = credentials; // << 여기
        super.setAuthenticated(true);
    }

Principal
1.주요한
2.학장
3.원금

Credentials
1. 자격
2. 자격, 인증서, 자격증

왜 Spring Security는 아이디/비밀번호 혹은 더 직관적인 단어가 있었을 텐데 뭐 이런 Principal, Credentials 와 같은 추상적인단어를 사용했을까

1. 추상화

처음 소스 코드를 봤을 때 Object principal이라는 타입이었다. '그냥 String으로 아이디 담으면 되는거 아닌감..' 이라는 생각을 했지만 Chat GPT에게 Principal이 주체/본인/당사자와 같은 의미도 가진다는 것을 발견하였다.

그럼 이제 이해가 간다
인증의 주체(Principal)은 아이디 뿐만이 아니라 이메일, 혹은 복합적인 객체일 수도 있다 라는 것을,
구체적인 타입에 의존하지 않음으로써 OCP(Open-Close Principle 개방 폐쇄 원칙)을 지킨다.
물론 이 추상화 때문에 개발자는 매번 CustomUserDetails와 같은 캐스팅을 해야 하는 비용을 지불하게 된다. 하지만 이 '불편한 유연성'덕분에 어떤 새로운 은증 수단이 들어와도 내부 엔진은 건들이지 않아도 된다는 점에서는 아름다운 설계라고 느껴진다.

2. 책임의 격리

시큐리티의 인증 과정은 SOC(Seperation Of Concern관심사 분리)를 따르고 있다.

이 구조는 우아하지만 나 같은 신입 개발자 입장에서는 코드가 여러 클래스로 흩어져 흐름을 파악하기 어렵다고 느껴진다(나만 그런가?). '단순히 로그인 하는데 왜 클래스를 3~4개나 거쳐야 하는거지?'라는 불만이 나오지만 흐름을 파악하고 각 클래스들을 들여다 보면 각자 하나의 책임만을 지키며 협력하고있다

인증 과정

  1. Authentication (전략 호출): 인증 토큰을 받으면 본인이 직접 검증하는 것이 아닌 AuthenticationProvider들에게 위임한다.
  2. UserDetailsService (데이터 조회): Provider는 DB 접근 책임자인 이 Service에게 "이 아이디를 가진 유저 정보(UserDetails)를 가져와"라고 요청한다 (조회 책임)
  3. UserDetails (데이터 반환): DB에서 꺼낸 아이디와 암호화된 비밀번호를 객체에 담아 반환한다.
  4. PasswordEncoder(검증): Provider는 이제 암호화 책임자를 불러 사용자가 입력한 비번과 DB에 저장된 비번을 비교한다. matches()메소드를 통해 암호화 알고리즘에 따른 일치 여부를 판별한다 (검증 책임)
  5. Authentication 객체 생성: 모든 검증이 끝나면 비로소 '인증 완료'(this.setAuthenticated(true);) 도장이 찍힌 최종 객체를 만들어 반환한다.

덕분에 DB가 바뀌든, 암호화 알고리즘이 바뀌든 각자의 영역은 견고하게 보호된다. 지금의 나는 단순히 로그인을 구현하기만 하지만 더욱 거대하고 복잡한 시스템으로 확장할 때 진가가 발휘되지 않을까 싶다.

profile
모르는 것에 부끄러워 말고 아는 것에 자만하지 말 것

0개의 댓글