"Spring MVC 기반 어플리케이션의 인증, 인가를 지원하는 보안 프레임 워크"
Interceptor
나 Servlet Filter
를 이용하여 보안 기능을 직접 구현가능하지만 Spring Security에서 안정적인 기능, 검증된 로직이기 때문에 Spring Security를 이용하는 것이 안전한 선택
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-security'
...
}
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// HttpSecurity를 통해 HTTP 요청에 대한 보안 설정을 구성
...
...
}
}
Spring Security 5.7 이전 버전에서는 WebSecurityConfigurerAdapter
를 상속받아 사용하였지만 5.7 버전에서 Deprecated되어 SecurityFilterChain
을 Bean으로 등록하여 HTTP 보안 설정을 구성해야 한다.
🤔 주의
.authorizeHttpRequests(authorize -> authorize
.antMatchers("/orders/**").hasRole("ADMIN")
.antMatchers("/members/my-page").hasRole("USER")
.antMatchers("/**").permitAll()
);
antMatchers()
를 이용한 접근 권한 부여 시 permitAll()이 제일 앞에 위치하면 Role에 상관없이 모든 request URL에 대한 접근을 허용한다. 항상 더 구체적인 URL 경로부터 접근 권한을 부여한 다음 덜 구체적인 URL 경로에 대한 접근 권한을 부여해야 한다.
어플리케이션의 엔드포인트에 요청이 도달하기 전에 중간에서 요청을 가로챈 후 어떤 처리를 할 수 있는 적절한 포인트를 제공, 요청의 전처리 뿐만 아니라 응답을 보내주기 전에 처리작업도 할 수 있다.
필터 체인
하나 이상의 필터들을 연결한 구조
Sevelet FilterChain은 요청 URI를 기반으로 HttpServletRequet를 처리한다. 클라이언트가 서버 측으로 요청을 하면 서블릿 컨테이너는 해당 URI의 경로를 기반으로 어떤 필터와 어떤 Servlet을 매핑할지 결정한다.
DelegatingFilterProxy
서블릿 컨테이너 영역의 필터와 ApplicationContext에 Bean으로 등록된 필터들을 연결해주는 브릿지 역할
import javax.servlet.*;
import java.io.IOException;
public class FirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("FirstFilter 생성됨");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("========First 필터 시작========");
chain.doFilter(request, response);
System.out.println("========First 필터 종료========");
}
@Override
public void destroy() {
System.out.println("FirstFilter Destory");
Filter.super.destroy();
}
}
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfiguration {
@Bean
public FilterRegistrationBean<FirstFilter> firstFilterRegister() {
FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>(new FirstFilter());
// registrationBean.setOrder(1);
return registrationBean;
}
}
FilterConfiguration에 여러 필터를 넣는다면 registrationBean.setOrder()
메서드를 이용하여 Filter가 적용되는 순서를 지정할 수 있다. 또는 Filter에 @Order
어노테이션을 이용하여 Filter의 순서를 지정할 수 있다.
Username(ID)와 password를 Spring Security의 Filter Chain중 UsernamePasswordAuthenticationFilter가 해당 요청을 받는다.
UsernamePasswordAuthenticationToken을 생성한다. 이때 아직 인증이 되지 않는다.
인증되지 않은 Authentication을 AuthenticationManager에게 전달한다. AuthenticationManager는 인증처리를 총괄하는 매니저 역할의 인터페이스이고 ProviderManager가 구현 클래스이다.
ProviderManager로부터 Authentication을 전달받은 AuthenticationProvider는 ProviderManager의 인증 처리를 대신한다.
UserDetailsService를 통해 UserDetails를 조회한다.
DB에 저장되어있는 사용자의 Credential(신원 정보)를 조회한다.
UserDetails를 생성한다.
AuthenticationProvider에게 전달한다.
AuthenticationProvider는 UserDetails를 검증하기 위해 PasswordEncoder를 이용하여 암호화된 Password의 일치여부를 검증한다.
검증에 성공하면 UserDetails를 이용하여 Authentication을 생성하고 그렇지 않으면 Exception을 발생시키고 인증처리를 중단한다.
ProviderManager에게 Authentication을 전달한다. 2번에서는 Authentication은 인증들 위해 필요한 사용자의 로그인 정보를 가지고 있지만 전달한 Authentication에는 Principal, Credential, GrantedAuthorities를 가지고 있다.
인증된 Authentication을 UsernamePasswordAuthenticationFilter에게 전달한다.
인증된 Authentication을 UsernamePasswordAuthenticationFilter에서 SecurityContext에 저장한다.
로그인을 성공한 이후에 인증된 사용자에게 권한을 부여하는지에 대한 흐름이다.
Spring Security Filter Chain에서 URL을 통해 사용자의 엑세스를 제한하는 권한 부여 Filter는 AuthorizationFilter이다.
인증을 무사히 마쳤다면 SecurityContext에 Authentication이 저장되어 있을 것이다. 이를 가져온다.
획득한 Authentication과 HttpServletRequest를 AuthorizationManager에게 전달한다.
AuthorizationManager는 RequestMatcherDelegatingAuthorizationManager에게 권한 부여를 위임한다. RequestMatcherDelegatingAuthorizationManager는 RequestMathcer 평가식을 기반으로 권한을 체크한다.
적절한 권한이라면 다음 요청 프로세스를 이어간다.
적절한 권한이 아니라면 AccessDeniedException이 throw 된다.
표현식 | 설명 |
---|---|
hasRole(Stirng role) | - 현재 보안 주체(principal)가 지정된 역할을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다. - hasRole(’admin’)처럼 파라미터로 넘긴 role이 ROLE_ 로 시작하지 않으면 기본적으로 추가한다. (DefaultWebSecurityExpressionHandler의 defaultRolePrefix를 수정하면 커스텀할 수 있다.) |
hasAnyRole(String… roles) | - 현재 보안 주체가 지정한 역할 중 1개라도 가지고 있으면 true를 리턴한다. (문자열 리스트를 콤마로 구분해서 전달한다.) - ex) hasAnyRole(’admin’, ‘user’) |
hasAuthority(String authority) | - 현재 보안 주체가 지정한 권한을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다. - ex) hasAuthority(’read’) |
hasAnyAuthority(String… authorities) | - 현재 보안 주체가 지정한 권한 중 하나라도 있으면 true를 리턴한다. - ex) hasAnyAuthority(’read’, ‘write’) |
principal | - 현재 사용자를 나타내는 principal 객체에 직접 접근할 수 있다. |
authentication | - SecurityContext로 조회할 수 있는 현재 Authentication 객체에 직접 접근할 수 있다. |
permitAll | - 항상 true로 평가한다. |
denyAll | - 항상 false로 평가한다. |
isAnonymous() | - 현재 보안 주체가 익명 사용자면 true를 리턴한다. |
isRememberMe() | - 현재 보안 주체가 remember-me 사용자면 true를 리턴한다. |
isAuthenticated() | - 사용자가 익명이 아닌 경우 true를 리턴한다. |
isFullyAuthenticated() | - 사용자가 익명 사용자나 remember-me 사용자가 아니면 true를 리턴한다. |
hasPermission(Object target, Object permission) | - 사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다. ex) hasPermission(domainObject, ‘read’) |
hasPermission(Object targetId, String targetType, Object permission) | - 사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다. ex) hasPermission(1, ‘com.example.domain.Message’, ‘read’) |