Spring Security는 Spring MVC 기반 애플리케이션의 보안 프레임워크로
인증(Authentication)과 권한부여(Authorization)기능을 지원합니다.
JAVA 보안 프레임워크에는 Spring Security, Apache Shiro, JAAS, Keycloak 등등이 있지만
Spring Security는 인증, 권한 부여, 세션 관리, 암호화, 보안 헤더 설정 등 다양한 측면을 다룰 수 있습니다.
또한, 필요에 따라 확장할 수 있는 유연한 아키텍처와 확장 포인트를 제공하여 개발자들이 요구 사항에 맞게 커스터마이징할 수 있습니다.
다른기술들과 상호작용이 원활해 데이터베이스, 인증 제공자, 캐시 시스템들과 통합이 쉽고, 스프링과 궁합이 잘 맞는 장점들이 있어서 많은 개발자들이 선택합니다.
<Spring Security로 할 수 있는 보안 강화 기능>
- 다양한 유형의 사용자 인증 기능 적용
(폼 로그인 인증, 토큰 기반 인증, OAuth 2 기반 인증, LDAP 인증)- 애플리케이션 사용자의 역할(Role)에 따른 권한 레벨 적용
- 애플리케이션에서 제공하는 리소스에 대한 접근 제어
- 민감한 정보에 대한 데이터 암호화
- SSL 적용
- 일반적으로 알려진 웹 보안 공격 차단
<Spring Security에서 사용하는 용어>
단어 | 의미 |
---|---|
Principal(주체) | 애플리케이션에서 작업을 수행할 수 있는 사용자, 디바이스 또는 시스템 등이 될 수 있으며, 일반적으로 인증 프로세스가 성공적으로 수행된 사용자의 계정 정보 |
Authentication(인증) | 사용자가 본인이 맞음을 증명하는 절차 ex)신분증검사, 로그인 |
Authorization(권한 부여) | 인증이 정상적으로 수행된 사용자에게 하나 이상의 권한(authority)을 부여하여 특정 애플리케이션의 특정 리소스에 접근할 수 있게 허가하는 과정 |
Access Control(접근 제어) | 사용자가 애플리케이션의 리소스에 접근하는 행위를 제어하는 것 |
(1) 사용자가 보호된 리소스를 요청하면
(2) 로그인(크리덴셜) 요청을 합니다.
(3) 사용자가 입력한 ID와 PW를 입력하면
(4) 인증관리자는 크리덴셜저장소(DB)의 정보를 조회합니다.
(5) 사용자가 제공한 크리덴셜과 크리덴셜 저장소에 저장된 크리덴셜을 비교해
검증 작업을 수행합니다.
(6) 유효한 크리덴셜이 아니라면 Exception을 throw 합니다.
(7) 유효한 크리덴셜이라면 접근 결정 관리자 역할을 하는 컴포넌트는
(8) 사용자가 적절한 권한을 부여받았는지 검증합니다.
(9) 적절한 권한을 부여받지 못한 사용자라면 Exception을 throw합니다.
(10) 적절한 권한을 부여받은 사용자라면 보호된 리소스의 접근을 허용합니다.
그렇다면 위의 보안 인증 작업이 언제 어디서 일어나는 것인지 살펴보겠습니다.
사용자의 크리덴셜과 접근 권한을 검증하는 부분은 서블릿 필터를 통해 이루어됩니다.
javax.servlet.Filter 인터페이스를 구현한 서블릿 필터는 웹 요청(request)을 가로채어 어떤 처리(전처리)를 할 수 있으며, 또한 엔드포인트에서 요청 처리가 끝난 후 전달되는 응답(reponse)을 클라이언트에게 전달하기 전에 어떤 처리(후처리)를 할 수 있습니다.
서블릿 필터는 클라이언트의 요청이 웹 애플리케이션의 엔드포인트(예: Controller)에 도달하기 전에 중간에서 요청을 가로채어 사용자가 제공한 크리덴셜을 검증하고, 인증 관리자나 접근 결정 관리자 같은 컴포넌트를 호출하여 인증과 권한 부여를 수행합니다.
여러개의 필터들은 dofilter()를 통해 서로 연결하여 필터체인으로 구성될 수 있고, 순차적으로 실행되는데 Spring Seucrity Filter는 서블릿 컨테이너가 아닌 IOC 컨테이너에서 ApplicationContext의 빈으로 관리되므로 필터들이 존재하는 영역이 다릅니다.
그래서 Spring Security 필터들은 서블릿 필터 안에 있는 DelegatingFilterProxy를 통해 연결되고 실행되며, 실제 필터 체인은 FilterChainProxy를 통해 구성됩니다.
FilterChainFroxy 내부에 SecurityFilterChain가 있다고 생각하면 됩니다.
필터들은 순차적으로 실행되는데, Filter는 나머지 Filter와 Servlet에 영향을 주기 때문에 필터의 실행 순서를 조절하여 요청을 보호하고 인증/인가 작업을 수행합니다.
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
Spring Security에서 지원하는 PasswordEncoderFactories 클래스를 이용하면 기본적으로 Spring Security에서 권장하는 PasswordEncoder를 사용할 수 있지만 필요한 경우, DelegatingPasswordEncoder로 직접 PasswordEncoder를 지정해서 Custom DelegatingPasswordEncoder를 사용할 수 있습니다.
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
// Map encoders에 원하는 유형의 PasswordEncoder를 추가해서
// DelegatingPasswordEncoder의 생성자로 넘겨주면
// 디폴트로 지정(idForEncode)한 PasswordEncoder를 사용할 수 있습니다.
(1) 로그인 요청이 오면 Spring Security 필터 체인은 요청을 받아서 가장 먼저 UsernamePasswordAuthenticationFilter를 실행합니다.
(2) 이 필터는 사용자가 제공한 사용자 이름(아이디)과 비밀번호를 추출해 토큰에 담습니다.
(3) 추출된 사용자 이름과 비밀번호를 기반으로 AuthenticationManager에게 인증 요청을 전달합니다.
(4) AuthenticationManager는 AuthenticationProvider를 사용하여 실제 인증 처리를 수행합니다.
(5)~(10) AuthenticationProvider는 사용자 이름과 비밀번호를 확인하고, 인증에 성공하면 Authentication 객체를 반환합니다.
(11)~(12) AuthenticationManager는 인증에 성공한 Authentication 객체를 SecurityContextHolder에 저장합니다. SecurityContextHolder는 현재 사용자의 인증 정보를 보관하는 역할을 합니다.
인증이 완료된 후, 요청은 다음 단계로 진행됩니다. 이후의 필터 체인에서는 인증된 사용자의 권한 검사 등을 수행할 수 있습니다.
요청을 처리하는 컨트롤러나 서비스 등에서는 SecurityContextHolder를 통해 현재 사용자의 인증 정보에 접근할 수 있습니다.
Spring Security Filter Chain에서 URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter는 바로 AuthorizationFilter입니다.
앞에서 인증 처리를 하면서 SecurityContextHolder에는 인증된 토큰이 저장되어있습니다.
SecurityContextHolder로부터 Authentication을 획득합니다.
그후 SecurityContextHolder로부터 획득한 Authentication과 HttpServletRequest를 AuthorizationManager에게 전달합니다.
AuthorizationManager는 인터페이스이고, RequestMatcherDelegatingAuthorizationManager는 이를 구현하는 구현체 중 하나입니다.
RequestMatcherDelegatingAuthorizationManager 내부에서 매치되는 AuthorizationManager 구현 클래스가 있다면 해당 AuthorizationManager 구현 클래스가 사용자의 권한을 체크합니다.
적절한 권한이라면 다음 요청 프로세서로 넘어가고 그렇지 않다면 AccessDeniedException이 throw되고 ExceptionTranslationFilter가 AccessDeniedException을 처리하게 됩니다.