학습목표
- 스프링 시큐리티
TIL
- 스프링 시큐리티
Spring Security와 동작 원리
Spring Security는 Java 기반의 애플리케이션에 대한 포괄적인 보안 서비스를 제공하는 프레임워크입니다. 웹 및 메소드 기반 보안, CSRF(크로스 사이트 요청 위조) 방지, 세션 관리, 사용자 등록, 인증 및 권한 부여 등 다양한 보안 관련 기능을 제공합니다.
Spring Security의 주요 동작 원리는 다음과 같습니다:
- Filter-based Approach: Spring Security는 일련의 Servlet Filters를 사용하여 보안을 구현합니다. 이 필터 체인은 보통 DelegatingFilterProxy를 통해 시작됩니다. 각 필터는 특정한 보안 관련 작업을 처리하며, 체인은 주로 먼저 정의된 순서대로 실행됩니다.
- Authentication: 사용자의 자격 증명을 확인하는 과정입니다. 이는 AuthenticationManager와 그 구현체인 ProviderManager에 의해 처리됩니다. AuthenticationManager는 다수의 AuthenticationProvider들로 구성됩니다. 각각의 AuthenticationProvider는 특정한 인증 방식(예: 폼 기반 인증, LDAP 인증 등)을 처리합니다.
- Authorization: 인증 후 사용자가 특정 리소스에 접근하려 할 때, 그 사용자가 해당 접근을 허용받았는지 확인하는 과정입니다. 이는 AccessDecisionManager에 의해 처리됩니다. AccessDecisionManager는 다수의 AccessDecisionVoter들로 구성되어 있으며, 각 AccessDecisionVoter는 사용자가 요청한 특정 작업에 대해 허용할 것인지 거부할 것인지를 투표하는 역할을 합니다.
- Security Context: Spring Security는 보안 관련 정보를 SecurityContextHolder라는 객체에 저장합니다. 이 객체에는 현재 인증된 사용자, 그들의 권한, 그리고 일부 세부 정보가 포함됩니다. 이 정보는 애플리케이션의 다른 부분에서 접근이 가능하여, 사용자의 권한에 따라 다른 로직을 실행할 수 있게 해줍니다.
이러한 원리를 통해 Spring Security는 애플리케이션의 보안을 철저히 관리할 수 있게 돕습니다. 이는 또한 개발자가 애플리케이션의 주요 로직에 집중하면서, 보안과 관련된 복잡성을 프레임워크에 맡기는 것을 가능하게 합니다.
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
이 코드 부분 알려줘 설명해주고 ㅎ 처음 보는 부분이라 잘 모르는게 많아
이 코드는 Spring Security에서 사용하는 인증 프로세스를 나타냅니다. AuthenticationProvider 인터페이스의 구현체를 통해 사용자 인증을 시도하는 것입니다.
각 AuthenticationProvider는 특정한 유형의 인증을 처리하도록 설계되어 있습니다. 예를 들어, 하나는 LDAP를 사용한 인증을 처리할 수 있고, 다른 하나는 폼 기반의 사용자 이름/비밀번호 인증을 처리할 수 있습니다.
이 코드에서는, 각 AuthenticationProvider가 주어진 Authentication 객체를 지원하는지 확인한 다음, 해당 AuthenticationProvider로 인증을 시도합니다. 만약 인증에 성공하면 인증 결과가 반환되고, 인증에 실패하면 AuthenticationException이 발생합니다.
코드를 간략하게 살펴보면:
- provider.supports(toTest): 각 AuthenticationProvider가 주어진 Authentication 유형을 지원하는지 확인합니다. 지원하지 않는 경우, 그 AuthenticationProvider는 건너뜁니다.
- provider.authenticate(authentication): AuthenticationProvider로 주어진 Authentication 객체를 인증하려고 시도합니다. 인증에 성공하면 인증 결과가 result에 저장됩니다.
- copyDetails(authentication, result): 이 메소드는 원본 Authentication 객체에서 세부 사항을 복사하여 결과 Authentication 객체에 적용합니다. 이는 예를 들어, 인증 요청에 대한 세부 사항을 유지하면서 인증 결과에 대한 추가 정보를 제공하는 데 사용됩니다.
- prepareException(ex, authentication): AccountStatusException 또는 InternalAuthenticationServiceException이 발생하는 경우, 이 메소드를 호출하여 예외를 처리합니다. 일반적으로 이는 로깅 또는 추가적인 예외 처리를 위한 것입니다.
이 코드는 이해하는데 어려울 수 있지만, Spring Security의 작동 원리를 이해하면 많은 도움이 됩니다.
AuthenticationProvider 이 인터페이스? 구현체? 부분은 AuthenticationProvider이 적용 될 수 있는 방식을 사용했는 확인하고 확인 후 가능하다면 진행. 진행과정은 인증을 시도해보고 성공하면 이 데이터에 다른 필요한 추가정보를 authentication객체에 저장. 인증 실패등 예외가 발생하면 예외처리하는 코드로 구성이 되어 잇는거네
맞습니다, 잘 이해하셨습니다!
AuthenticationProvider는 인터페이스이며, 이를 구현한 구현체들은 각각 다양한 방식으로 인증을 처리합니다. 코드는 이러한 AuthenticationProvider 구현체들을 순회하면서 인증을 시도하게 됩니다.
그리고 provider.supports(toTest)를 통해 해당 AuthenticationProvider가 특정 Authentication 유형을 지원하는지 확인합니다. 만약 지원한다면, provider.authenticate(authentication)를 호출하여 인증을 시도합니다.
인증이 성공하면, 그 결과는 result에 저장되며, 이는 원래 Authentication 객체로부터 세부 정보를 복사하는 copyDetails(authentication, result) 메서드를 통해 반환됩니다.
그리고 인증 과정에서 예외가 발생하면 이를 적절히 처리하기 위해 예외 처리 부분이 존재합니다.
따라서 이 코드는 각 AuthenticationProvider를 통해 인증을 시도하고, 그 결과를 처리하는 데 필요한 모든 로직을 포함하고 있습니다. 이는 Spring Security에서 중요한 역할을 하는 코드 부분이므로 이해하는 데 도움이 될 것입니다.
DelegatingFilterProxy
Spring Security에서는 Servlet 필터 체인을 사용하여 보안을 구현합니다. 이 필터 체인의 진입점은 대개 DelegatingFilterProxy라는 특별한 서블릿 필터입니다.
DelegatingFilterProxy는 실제 보안 처리 로직이 구현된 Spring의 bean에게 요청을 위임하는 역할을 합니다. 이 필터 자체는 Spring Security의 보안 로직을 직접 구현하지 않습니다. 대신, 필터가 활성화되면 요청은 이 필터를 통과하고, 이 필터는 실제 보안 처리 로직이 구현된 Spring의 bean에게 처리를 위임합니다.
이렇게 하면 웹 애플리케이션의 web.xml이나 Spring Boot의 자동 설정 같은 곳에서 간단하게 필터를 설정할 수 있습니다. 그리고 실제 보안 로직은 Spring 컨텍스트 안에서 관리되는 bean에서 처리할 수 있습니다.
예를 들어, 아래와 같이 web.xml에서 DelegatingFilterProxy를 설정할 수 있습니다:
<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>
위 예에서 springSecurityFilterChain은 실제 보안 처리 로직을 담당하는 Spring Security의 필터 체인 bean의 이름입니다. DelegatingFilterProxy는 이 bean에게 요청 처리를 위임합니다. 이 방식으로 웹 애플리케이션의 보안 처리를 Spring 컨텍스트 안에서 손쉽게 관리할 수 있습니다.
DelegatingFilterProxy는 월래 필터 구역에 존재하는 필터들과는 다르게 스프링에 있는 보안 로직을 끌어다가 쓰기 위해 특별히 구현된 하나의 필터인 거지?? 그것이 어떻게 가능해?
DelegatingFilterProxy는 이름에서 알 수 있듯이 Proxy 패턴을 사용합니다. Proxy 패턴은 실제 로직이 구현된 객체 대신 그 객체의 대리자(Proxy)를 사용하는 디자인 패턴입니다. 이를 통해 객체가 직접 수행하는 작업을 대신 처리하거나, 실제 객체에 대한 접근을 제어할 수 있습니다.
서블릿 컨테이너는 web.xml 파일이나 어노테이션 기반 설정을 통해 필터를 인식하고 활성화합니다. 이때 서블릿 컨테이너는 필터 인스턴스를 직접 생성하고 관리합니다. 그러나 DelegatingFilterProxy는 Spring의 ApplicationContext에서 관리되는 Spring bean에게 실제 작업을 위임합니다.
이것이 가능한 이유는, DelegatingFilterProxy가 필터의 init() 메서드에서 Spring의 ApplicationContext를 로드하고, 그곳에서 특정 이름의 bean을 찾기 때문입니다. 그리고 필터의 doFilter() 메서드에서 이 찾은 bean의 메서드를 호출하여 실제 작업을 수행합니다.
즉, DelegatingFilterProxy는 다음과 같은 흐름으로 동작합니다:
- 서블릿 컨테이너는 DelegatingFilterProxy 인스턴스를 생성하고 init() 메서드를 호출합니다.
- DelegatingFilterProxy의 init() 메서드는 Spring의 ApplicationContext를 로드하고, 설정에서 지정한 이름의 Spring bean을 찾습니다.
- 서블릿 컨테이너가 doFilter() 메서드를 호출하면, DelegatingFilterProxy는 이 메서드 호출을 찾아놓은 Spring bean에게 위임합니다.
- Spring bean은 실제 보안 처리 로직을 수행합니다.
이런 방식으로 DelegatingFilterProxy는 서블릿 컨테이너가 관리하는 필터 체인에서 Spring의 보안 로직을 수행할 수 있게 해줍니다.
다른 필터들을 어떻게 동작해?
서블릿 필터는 Java EE 웹 애플리케이션의 일부분으로, 클라이언트에서 서버로 가는 요청과 서버에서 클라이언트로 가는 응답을 가로채서 처리하는 역할을 합니다. 필터는 일반적으로 로깅, 인증, 인가, 데이터 압축, 인코딩 등 다양한 목적으로 사용됩니다.
서블릿 필터의 동작 방식은 다음과 같습니다:
- Initialization(초기화): 필터가 처음 생성될 때 서블릿 컨테이너에 의해 init() 메서드가 호출됩니다. 이 메서드는 필터 설정에 관한 정보를 포함하는 FilterConfig 객체를 인수로 받습니다. 필터의 초기 설정이 이 메서드에서 이루어집니다.
- Filtering(필터링): 클라이언트의 요청이 필터에 의해 가로채지는 과정입니다. 이때는 doFilter() 메서드가 호출됩니다. doFilter() 메서드는 요청과 응답 객체 (ServletRequest와 ServletResponse), 그리고 필터 체인 (FilterChain)을 인수로 받습니다. 필터는 doFilter() 메서드 안에서 요청이나 응답을 조작하거나, 추가적인 로직을 실행할 수 있습니다. 그리고 마지막으로 FilterChain.doFilter()를 호출함으로써 다음 필터로 요청과 응답을 전달합니다.
- Destruction(소멸): 필터의 생명 주기가 끝나면, 즉 웹 애플리케이션이 종료되면 서블릿 컨테이너에 의해 destroy() 메서드가 호출됩니다. 이 메서드에서 필터가 사용한 자원을 정리하는 작업을 수행합니다.
위와 같은 방식으로 서블릿 필터는 요청과 응답 사이에서 작동하며, 다양한 목적에 따라 요청이나 응답을 조작하거나 가공하는 역할을 합니다.
여기서 말한 서블릿 컨테이너와 Spring 컨텍스트에 관해 알려줘
서블릿 컨테이너와 Spring 컨텍스트는 Java 웹 애플리케이션에서 중요한 역할을 하는 컴포넌트들입니다.
서블릿 컨테이너는 서블릿의 생명주기를 관리하고, HTTP 요청을 처리하는 데 필요한 환경을 제공하는 컴포넌트입니다. 서블릿 컨테이너는 웹 서버와 통합되어 동작하며, 대표적인 서블릿 컨테이너로는 Tomcat, Jetty, Glassfish 등이 있습니다. 서블릿 컨테이너는 웹 애플리케이션을 배포하고, 클라이언트의 요청에 따라 서블릿을 로드하고, 서블릿의 초기화 및 서비스 메소드를 호출하며, 애플리케이션 종료 시에는 서블릿의 소멸 메소드를 호출합니다.
Spring 컨텍스트는 Spring 애플리케이션에서 객체의 생성과 관리를 담당하는 IoC(Inversion of Control) 컨테이너입니다. IoC는 객체의 생명주기와 설정을 개발자가 아닌 프레임워크가 관리하는 디자인 패턴입니다. 이를 통해 개발자는 객체간의 의존성을 걱정하지 않고 비즈니스 로직에 집중할 수 있습니다. Spring 컨텍스트는 bean 정의를 XML, 자바 어노테이션, 자바 코드 등을 통해 읽어 들인 후, 이 정보를 바탕으로 bean 인스턴스를 생성하고, 의존성 주입을 수행하며, bean의 생명주기를 관리합니다.
따라서 서블릿 컨테이너와 Spring 컨텍스트는 각각 웹 애플리케이션의 HTTP 요청 처리와 객체의 생성 및 관리라는 서로 다른 책임을 담당합니다. 이 두 컴포넌트는 서로 독립적으로 동작하지만, 서블릿 컨테이너에서 Spring 컨텍스트를 로드하고, Spring 컨텍스트에서 생성한 객체를 서블릿 컨테이너의 서블릿이나 필터로 사용할 수 있도록 연동되어 동작합니다.
DelegatingFilterProxy는 톰캣의 서블릿 컨테이너의 영역에 있지만 spring 컨텍스트의 빈을 호출하는 것인가?
네, 맞습니다. DelegatingFilterProxy는 서블릿 컨테이너가 관리하는 필터이지만, 실제 작업을 처리하는 구현체는 Spring 컨텍스트에서 관리하는 Spring bean입니다.
DelegatingFilterProxy는 서블릿 필터 체인에서 작동하지만, 실제 필터 로직을 Spring 컨텍스트의 bean에게 위임하는 방식으로 동작합니다. 이를 통해 Spring 컨텍스트에서 관리되는 보안 관련 구현체를 서블릿 필터 체인에서 사용할 수 있게 됩니다.
이렇게 하면, Spring Security와 같은 보안 로직을 Spring의 DI(Dependency Injection)와 AOP(Aspect Oriented Programming) 등의 기능을 이용해서 관리할 수 있습니다. 또한, 일반적인 Spring bean처럼 보안 관련 구현체를 테스트하거나 재사용할 수 있습니다.
따라서 DelegatingFilterProxy는 서블릿 컨테이너와 Spring 컨텍스트 간의 연결 고리 역할을 하며, 이 두 영역 사이에서 보안 관련 작업을 쉽게 이동시킬 수 있게 해줍니다.
만약 톰캣이 아니라 Nginx였으면 스프링 시큐리티는 못쓰는 거야?
Nginx 자체는 웹 서버와 리버스 프록시 서버로 주로 사용되며, 자체적으로 Java 서블릿을 실행시키는 기능이 없습니다. 따라서 Nginx만으로는 Spring Security를 직접 사용할 수 없습니다.
그러나 일반적인 배포 시나리오에서 Nginx는 정적 컨텐츠를 처리하고, 동적 컨텐츠는 Java 애플리케이션 서버(ex: Tomcat, Jetty, etc.)에게 전달하는 리버스 프록시의 역할을 합니다. 이렇게 배포된 경우 Spring Security는 Java 애플리케이션 서버에서 동작하여 인증 및 인가 등의 보안 관련 작업을 처리합니다.
따라서 Nginx와 Spring Security를 함께 사용하려면, Nginx 뒤에 Java 애플리케이션 서버를 배치하고, 이 서버에서 Spring Security를 사용하는 것이 일반적입니다. Nginx는 클라이언트와의 연결 및 정적 컨텐츠의 처리, 로드 밸런싱 등을 담당하며, Java 애플리케이션 서버는 동적 컨텐츠의 처리 및 보안 관련 작업을 담당합니다.
'[F-Lab 66해빗 페이백 챌린지 ]' 카테고리의 다른 글
| [F-Lab 모각코 페이백 31일차] AOP(Aspect-Oriented Programming) (0) | 2023.07.13 |
|---|---|
| [F-Lab 모각코 페이백 29일차] ORM (Object-Relational Mapping) (0) | 2023.07.11 |
| [F-Lab 모각코 페이백 26일차] 스프링 트랜잭션 (Spring Transaction) -2 (0) | 2023.06.13 |
| [F-Lab 모각코 페이백 25일차] ORM, Spring MVC (작동과정과 컴포넌트) (0) | 2023.06.12 |
| [F-Lab 모각코 페이백 24일차] 팀코칭 질문 정리 (인터뷰 준비, 개념 학습) 쿠키, 세션, JDBC, 트랜잭션 (3) | 2023.06.09 |