// Developer · Debugging Reports

뉴스도 디버깅이 필요해

마주친 오류를 재현하고, 원인을 추적하고, 기록합니다. Java · Spring · DB · 실무 트러블슈팅.

debug — today.log
$ debug --trace "오늘의 뉴스"
> 헤드라인 파싱 중...
> 원인 추적: 근거 누락 · 출처 불명
> 패치 적용: 사실 확인 + 출처 보강
✓ RESOLVED
TOTAL
TODAY
YESTERDAY

Categories

웹개발/Spring

Spring Security 4.x XML 설정 정리 (공공 SI 기준)

자바를잡아2026. 7. 5. 08:00
반응형

왜 아직 Spring Security 4.x XML 설정인가

신규 프로젝트라면 최신 Spring Security를 Java Config로 설정하는 것이 정석이다. 그런데 공공 SI 유지보수 현장에 가면 Spring Security 4.x를 XML로 설정한 시스템을 여전히 자주 만난다. 전자정부 표준프레임워크(eGovFramework)의 특정 버전대가 이 조합을 기반으로 했고, 한 번 구축된 시스템은 오래 운영되기 때문이다. 그래서 Spring Security 4.x XML 설정을 읽고 손볼 줄 아는 것은 레거시 유지보수에서 실질적인 역량이 된다.

다만 분명히 해 둘 점이 있다. Spring Security 4.x는 이미 지원이 종료된 구버전이다. 신규 개발에 새로 도입할 이유는 없고, 가능하면 상위 버전으로의 마이그레이션을 검토하는 것이 보안상 바람직하다. 이 글은 "신규 도입"이 아니라 "기존 4.x 설정을 이해하고 유지보수하는" 관점에서 정리한다. 버전에 따라 네임스페이스나 속성이 다를 수 있으므로, 실제 프로젝트의 버전을 먼저 확인한다.

설정 구성 요소 한눈에 보기

요소 역할 위치
springSecurityFilterChain 보안 필터 진입점 web.xml
<http> 보안 정책 전체 정의 security-context.xml
<intercept-url> URL별 접근 권한 <http> 내부
<form-login> 로그인 폼·처리 설정 <http> 내부
<logout> 로그아웃 처리 <http> 내부
authentication-manager 인증 처리(사용자 조회) security-context.xml

web.xml에 보안 필터 등록

Spring Security는 서블릿 필터로 동작한다. 모든 요청이 보안 필터 체인을 거치도록 web.xml에 springSecurityFilterChain을 등록한다. 이름은 정해진 값이므로 그대로 쓴다.

<!-- web.xml -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

이 필터가 실제 보안 처리를 security-context.xml에 정의된 빈에 위임한다. security-context.xml은 ContextLoaderListener가 읽는 설정(contextConfigLocation)에 포함돼야 한다. 설정 파일이 로딩 목록에서 빠지면 보안이 아예 적용되지 않으니 확인한다.

security-context.xml 핵심 설정

1. http와 intercept-url

<http> 안에서 URL별 접근 권한을 <intercept-url>로 정의한다. 패턴은 위에서부터 순서대로 평가되므로, 구체적인 경로를 위에, 넓은 경로를 아래에 둔다.

<!-- security-context.xml -->
<beans:beans xmlns="http://www.springframework.org/schema/security" ...>

  <http auto-config="true" use-expressions="true">
      <!-- 순서 중요: 구체적 → 일반 -->
      <intercept-url pattern="/login.do" access="permitAll" />
      <intercept-url pattern="/css/**" access="permitAll" />
      <intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />
      <intercept-url pattern="/**" access="isAuthenticated()" />

      <form-login ... />
      <logout ... />
  </http>
</beans:beans>

use-expressions="true"hasRole(), permitAll, isAuthenticated() 같은 표현식을 쓸 수 있다. permitAll은 누구나 접근(로그인 페이지·정적 리소스), hasRole('ADMIN')은 ADMIN 권한 필요, isAuthenticated()는 로그인한 사용자면 허용이라는 뜻이다. 참고로 hasRole('ADMIN')은 내부적으로 ROLE_ADMIN 권한과 매칭되는 점에 유의한다(접두어 규칙).

2. form-login과 logout

로그인 폼과 처리 URL, 성공·실패 시 이동 경로를 <form-login>에 지정한다. 로그아웃은 <logout>으로 처리한다.

<form-login
    login-page="/login.do"
    login-processing-url="/loginProc.do"
    username-parameter="userId"
    password-parameter="password"
    default-target-url="/main.do"
    authentication-failure-url="/login.do?error=true" />

<logout
    logout-url="/logout.do"
    logout-success-url="/login.do"
    invalidate-session="true" />

<!-- 레거시 화면 구조에 따라 CSRF 설정 확인 필요 -->

로그인 폼의 name(userId/password)과 username-parameter·password-parameter가 일치해야 한다. login-processing-url은 실제 로그인을 처리하는 가상 URL로, 이 경로의 처리는 Spring Security가 맡으므로 별도 컨트롤러를 만들지 않는다. 초급자가 여기에 컨트롤러를 만들려다 헷갈리는 경우가 많다.

authentication-manager: 사용자 인증

실제로 아이디/비밀번호를 검증하는 부분이다. 사용자 정보를 어디서 가져올지(UserDetailsService)와 비밀번호 인코더를 연결한다. 공공 시스템은 DB에서 사용자를 조회하는 커스텀 서비스를 쓰는 경우가 많다.

<!-- 비밀번호 인코더 -->
<beans:bean id="passwordEncoder"
    class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

<!-- 사용자 조회 서비스 (DB 연동 커스텀 구현) -->
<authentication-manager>
    <authentication-provider user-service-ref="customUserDetailsService">
        <password-encoder ref="passwordEncoder" />
    </authentication-provider>
</authentication-manager>

<beans:bean id="customUserDetailsService"
    class="com.example.security.CustomUserDetailsService" />

비밀번호는 평문 비교가 아니라 인코더로 해시 비교해야 한다. 기존 시스템이 평문이나 약한 해시를 쓰고 있다면 보안 위험이므로, 마이그레이션 시 인코딩 방식 전환을 함께 검토한다. CustomUserDetailsService는 DB에서 사용자와 권한을 조회해 Spring Security가 이해하는 형태로 돌려주는 역할을 한다.

레거시 4.x를 다룰 때 주의점

  • 버전별 차이 — Security 3.x→4.x→5.x에서 네임스페이스·기본 동작·속성이 바뀌었다. 4.x 기준 설정을 다른 버전 자료와 섞으면 오류가 난다. 프로젝트 버전을 먼저 확정한다.
  • CSRF 기본 활성화 — Security 4.x부터 CSRF 보호가 기본 켜짐이다. 폼에 CSRF 토큰이 없으면 POST가 차단될 수 있다. 화면에 토큰을 넣거나 정책을 확인한다.
  • intercept-url 순서 — 위에서부터 매칭되므로 순서가 틀리면 권한이 의도와 다르게 적용된다.
  • 설정 파일 로딩 — security-context.xml이 context 로딩 목록에 빠지면 보안이 적용되지 않는다.
  • 지원 종료 — 4.x는 보안 패치가 끝난 버전이다. 운영 시스템이라면 상위 버전 마이그레이션을 장기 과제로 검토한다.

FAQ

Q1. springSecurityFilterChain 이름을 바꿔도 되나요?

기본적으로 정해진 이름이다. DelegatingFilterProxy가 이 이름의 빈을 찾으므로 그대로 쓰는 것이 안전하다. 바꾸려면 추가 설정이 필요하다.

Q2. login-processing-url에 컨트롤러를 만들어야 하나요?

아니다. 이 URL의 로그인 처리는 Spring Security가 담당한다. 별도 컨트롤러를 만들 필요가 없고, 폼의 action만 이 URL로 맞춘다.

Q3. hasRole('ADMIN')인데 권한이 안 맞습니다.

내부적으로 ROLE_ 접두어와 매칭된다. DB나 UserDetailsService에서 권한을 ROLE_ADMIN 형태로 부여하는지 확인한다.

Q4. POST 요청이 403으로 막힙니다.

Security 4.x는 CSRF 보호가 기본 활성화다. 폼에 CSRF 토큰이 빠졌을 가능성이 크다. 토큰을 추가하거나 CSRF 정책을 확인한다.

Q5. 4.x 설정을 최신 버전 자료대로 했더니 오류가 납니다.

버전 간 네임스페이스·속성·기본 동작이 다르다. 4.x 기준 자료로 맞춰야 한다. 가능하면 버전 마이그레이션을 별도로 검토한다.

정리

Spring Security 4.x XML 설정은 "web.xml에 springSecurityFilterChain을 등록하고, security-context.xml의 <http>에서 intercept-url로 권한을, form-login·logout으로 로그인 흐름을, authentication-manager로 인증을 정의하는" 구조다. intercept-url은 구체적 경로를 위에 두는 순서가 중요하고, hasRole은 ROLE_ 접두어 규칙을, 4.x는 CSRF 기본 활성화를 기억해야 한다. 다만 4.x는 지원이 종료된 구버전이라 신규 도입 대상이 아니며, 이 글은 레거시 유지보수 관점의 해석·수정에 초점을 둔다. 운영 시스템이라면 보안을 위해 상위 버전 마이그레이션을 장기 과제로 검토하고, 설정 변경은 버전을 확인한 뒤 테스트 환경에서 먼저 적용하는 것이 안전하다.

반응형