9주차 모각코 학습결과(22.08.30 화 09:00~12:00)
스프링 시큐리티
: 스프링 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크
즉, 인증(누구인지)과 인가(어떤 것을 할 수 있는지)를 담당하는 프레임워크
주로 서블릿 필터와 이들로 구성된 필터체인으로의 구성된 위임모델을 사용한다. 그리고 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있다.
보안 언어 정리
- 인증 (Authenticate) : 접근하려는 유저가 누구인지 확인하는 절차, 애플리케이션의 작업을 수행해도 되는 주체인지 확인하는 과정 => 누구인지 확인 ex) 회원가입하고 로그인
- 인가 (Authorization) : 인증된 사용자에 대해서 권한을 확인하고 허락하는 것 => 어떤 것을 할 수 있는지 확인
- 접근 주체 (Principal) : 보호된 대상에 접근하는 유저
- 비밀번호 (Credential) : 대상에 접근하는 유저의 비밀번호
- 권한 : 어떠한 리소스에 대한 접근 제한, 모든 리소스는 접근 제어 권한이 걸려 있음. 인가 과정에서 해당 리소스에 대한 제한된 최소한의 권한을 가졌는지 확인
- 인증과 인가에 대해서 예시를 들어보면
- 한 사용자가 velog에 글을 작성하기 위해 로그인을 했다 => 인증
- 로그인 한 사용자가 신나게 글을 썼다. 그리고 다른 사람 글을 수정하기 위해서 권한을 확인해봤지만 수정할 수 없었다. => 인가
- Spring Security에서는 이러한 인증, 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반의 인증방식을 사용한다.
스프링 시큐리티 특징과 구조
- 보안과 관련하여 체계적으로 많은 옵션을 제공하여 편리하게 사용할 수 있음
- Filter 기반으로 동작하여 MVC와 분리하여 관리 및 동작
- 어노테이션을 통한 간단한 설정
- Spring Security는 기본적으로 세션 & 쿠키방식으로 인증
- 인증관리자(Authentication Manager)와 접근 결정 관리자(Access Decision Manager)를 통해 사용자의 리소스 접근을 관리
- 인증관리자는 UsenamePasswordAuthenticationFilter, 접근 결정 관리자는 FilterSecurityInterceptor가 수행
서블릿 필터
스프링 시큐리티는 서블릿의 필터를 기반으로 동작한다.
사용자의 요청이 서블릿에 전달되기 전, 스프링 시큐리티는 필터의 생명주기를 이용해서 인증과 권한 작업을 수행하지만, 서블릿 컨테이너는 스프링 컨테이너에 등록된 빈을 인식할 수 없다.
그렇기 때문에 스프링 시큐리티에서는 DelegatingFilterProxy라는 서블릿 필터의 구현체를 제공한다.
DelegatinFilterProxy는 서블릿 매커니즘으로 서블릿의 필터로 등록될 수 있으며, 스프링에 등록된 빈을 가져와서 의존성을 주입할 수도 있다.
결론적으로 서블릿 컨테이너의 생명주기와 스프링의 ApplicationContext 사이를 연결하는 다리 역할을 하게 된다.
FilterChainProxy
: DelegatingFilterProxy를 통해 받은 요청과 응답을 스프링 시큐리티 필터체인에 전달하고 작업을 위임하는 역할
Q. DelegatingFilterProxy에서 바로 SecurityFilterChain을 실행시킬 수 있지만 중간에 FilterChainProxy를 둔 이유는?
A. 서블릿을 지원하는 시작점 역할을 하기 위해서. 이를 통해 서블릿에서 문제가 발생하는 경우 FilterChainProxy의 문제라는 걸 알 수 있다. 또한, FilterChainProxy에서 어떤 체인에게 작업을 위임할지도 결정할 수 있음
SecurityFilterChain
: 인증을 처리하는 여러 개의 시큐리티 필터를 담는 필터 체인
여러개의 SecurityFilterChain을 구성하여 매칭되는 URL에 따라 다른 SecurityFilterChain이 사용되도록 할 수 있다.
SecurityFilters
: 요청을 스프링 시큐리티 매커니즘에 따라 처리하는 필터
: SecurityFilters에는 순서가 존재함
아키텍처
: Username and Password 방식의 아키텍처는 다음과 같다. Spring Security는 기본적으로 세션-쿠키 방식으로 인증한다.
- 유저가 로그인을 요청(HTTP Request)
- AuthenticationFilter 에서 UsernamePasswordAuthentication Token을 생성하여 AuthenticationManager 에 전달
- AuthenticationManager은 등록된 AuthenticationProvider들을 조회하여 인증 요구
- AuthenticaitonProvider은 UserDetailService를 통해 입력받은 아이디에 대한 사용자 정보를 User(DB)에서 조회
- User 에 로그인 요청한 정보가 있는 경우 UserDetails로 꺼내서 유저 session을 생성
- 인증이 성공된 UsernameAuthenticationToken을 생성하여 AuthenticationManager로 반환
- AuthenticationManager은 UsernameAuthenticationToken을 AuthenticationFilter로 전달
- AuthenticaionFilter은 전달받은 UsernameAuthentication 을 LoginSuccessHandler로 전송하고, spring security 인메모리 세션저장소인 SecurityContextHolder에 저장
- 유저에게 session ID와 응답을 내려줌
1. AuthenticationFilter
- Spring Security는 연결된 필터를 가지고 있음
- 모든 Request는 인증과 인가를 위해서 이 필터를 통과
- SecurityContext에 사용자의 세션 ID가 있는지 확인하고 세션 ID가 없는 경우 다음 로직 수행
- 인증 성공하는 경우 인증된 Authentication 객체를 SecurityContext에 저장 후 AuthenticationSuccessHandler 실행
- 인증 실패하는 경우 AuthenticationFailureHandler 실행
2. UsernamePasswordAuthenticationToken
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken{
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION;
private final Object principal;
private Object credentials;
// This constructor can be safely used by any code that wishes to create a UsernamePasswordAuthenticationToken, as the isAuthenticated() will return false.
// 인증 전의 객체를 생성
public UsernamePasswordAuthenticaitonToken(Object principal, Object credentials){
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
// This constructor should only be used by AuthenticationManager or AuthenticationProvider implementations that are satisfied with producing a trusted (i.e. isAuthenticated() = true) authentication token.
// Params : principal -
// credentials -
// authorities -
// 인증 완료된 객체를 생성
public UsernamePasswordAuthenticaitonToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities){
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we overrride
}
}
- Authentication을 구현한 AbstractAuthenticationToken의 하위 클래스
- principal => username
- credentials => password
3. AuthenticationManager
- Authentication 을 만들고 인증을 처리하는 interface
- 로그인시 인자로 받은 Authentication을 Provider를 통해 유효한지 처리하여 Authenticaion 객체를 리턴한다.
public interface AuthenticationManager{
Authentication authenticate(Authentication authenticaion) throws AuthenticationException;
}
ProviderManager
- AuthenticationManager 의 구현체
- 사용자 요청을 인증에 필요한 AuthenticationProvider를 살펴보고 전달된 인증 객체를 기반으로 사용자 인증 시도
4. AuthenticaionProvider
- 실제 인증을 담당하는 인터페이스
- 인증 전 Authentication 객체를 받아 DB에 있는 사용자 정보를 비교하고 인증된 객체를 반환
5. UserDetailsService
public interface UserDetailsService{
/* Locates the user based on the username. In the actual implementation, the search may possibly be case sensitive, or case insensitive depending on how the implementation instance is configured. In this case, the UserDetails object that comes back may have a username that is of a different case than what was actually requested..
Params : username - the username identifying the user whose data is required.
Returns : a fully populated user record (never null)
Throws : UsernameNotFoundException - if the user could not be found or the user has no GrantedAuthority
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
- DB에서 유저 정보를 가져오는 역할
- loadUserByUsername() 메소드를 통해서 DB에서 유저 정보를 가져온다.
- 커스텀하게 사용하고 싶다면 해당 interface를 implements 받아서 loadUserByUsername() 메소드를 구현하면 됨
6. UserDetails
- 사용자의 정보를 담는 인터페이스
- 기본 오버라이드 메소드
- getAuthorities() : 계정의 권한 목록을 리턴
- getPassword() : 계정의 비밀번호를 리턴
- getUsername() : 계정의 고유한 값 리턴
- isAcountNonExpired() : 계정의 만료 여부 리턴
- isAcountNonLocked() : 계정의 잠김 여부 리턴
- isCredentialsNonExpired() : 비밀번호 만료 여부 리턴
- isEnable() : 계정의 활성화 여부 리턴
7. SecurityContextHolder
SecurityContextHolder
- SecurityContext를 현재 스레드와 연결 시켜주는 역할
- 스프링 시큐리티는 같은 스레드의 어플리케이션 내 어디서든 SecurityContextHolder의 인증 정보를 확인 가능하도록 구현되어 있는데 이 개념을 ThreadLocal 이라고 함.
SecurityContext
public interface SecurityContext extends Serializable{
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
- Authentication의 정보를 가지고 있는 interface
- SecurityContextHolder.getContext()를 통해 얻을 수 있음
Authentication
- 현재 접근하는 주체의 정보와 권한을 담는 인터페이스
- AuthenticationManager.authenticate(Authentication)에 의해 인증된 principal 혹은 token
- + Principal : 사용자 정보 + authorities : 사용자에게 부여된 권한 ex ) ROLE_ADMIN + credentials : 자격 증명
스프링 시큐리티 기본구조
- 각 필터별 기능 설명
- SecurityContextPersistenceFilter : SecurityContextRepository에서 SecurityContext를 로드하고 저장하는 일을 담당
- LogoutFilter : 로그아웃 URL로 지정된 가상 URL에 대한 요청을 감시하고 매칭되는 요청이 있으면 사용자를 로그아웃시킴
- UsernamePasswordAuthenticationFilter : 사용자명과 비밀번호로 이뤄진 폼기반 인증에 사용하는 가상 URL요청을 감시하고 요청이 있으면 사용자의 인증을 진행함
- DefaultLoginPageGeneratingFilter : 폼기반 또는 OpenID 기반 인증에 사용하는 가상 URL에 대한 요청을 감시하고 로그임 폼 기능을 수행하는 데 필요한 HTML을 생성함
- BasicAuthenticationFilter : HTTP 기본 인증 헤더를 감시하고 이를 처리함
- RequestCacheAwareFilter : 로그인 성공 이후 인증 요청에 의해 가로채어진 사용자의 원래 요청을 재구성하는 데 사용됨SecurityContextHolderAwareRequestFilter HttpServeltRequest를 HttpServeltRequestWrapper를 상속하는 하위 클래스(SecurityContextHolderAwareRequestWrapper)로 감싸서 필터 체인상 하단에 위치한 요청 프로세서에 추가 컨텍스트를 제공함
- AnonymousAuthenticaionFilter : 이 필터가 호출되는 시점까지 아직 인증을 받지 못했다면 요청 관련 인증 토큰에서 사용자가 익명 사용자로 나타나게 됨
- SessionManagementFilter : 인증된 주체를 바탕으로 세션 트래킹을 처리해 단일 추제와 관련한 모든 세션들이 트래킹 되도록 도움
- ExceptiontTranslationFilter : 이 필터는 보호된 요청을 처리하는 동안 발생할 수 있는 기대한 예외의 기본 라우팅과 위임을 처리함
- FilterSecurityInterceptor : 이 필터는 권한 부여와 관련한 결정을 AccessDecisionManager에게 위임해 권한부여 결정 및 접근 제어 결정을 쉽게 만들어 줌
스프링 시큐리티 기본 개념에 대해 알게 되었다.
출처 : https://velog.io/@soyeon207/SpringBoot-스프링-시큐리티란
https://devuna.tistory.com/55
https://limdevbasic.tistory.com/19