본문 바로가기

Spring/Security

SpringSecurity 스프링 시큐리티를 이용한 인증 및 인가 관리 개념

스프링 시큐리티(SpringSecurity)

증, 인가, 일반적인 공격에 대한 보호를 제공하기 위한 프레임워크

 

스프링 시큐리티는 '인증'과 '권한'에 대한 부분을 필터(Filter) 흐름에 따라 처리하고 있다.

 

필터는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, (웹 컨테이너에서 관리)

인터셉터(Interceptor)는 Dispatcher와 Controller 사이에 위치한다는 점에서 적용 시기의 차이가 있다. (스프링 컨테이너에서 관리)

 

인증 구조 및 과정 

 

주요 모듈 별 상세 역할

 

인증 필터 (AuthenticationFilter)

모든 요청에 대하여 AuthenticationFilter가 인증 및 권한 부여 과정을 거친 후 Dispatcher Servlet 으로 요청을 넘긴다.

 

최초 요청에 대해서 spring 은 filter 의 attempAuthentication 메소드를 요청하는데, 다시 filter 는 Token 을 생성해서 저장되어있는 authenticationManager 에게 검증 요청을 한다

AuthenticationFilter 는 Authentication 인터페이스를 구현한 Token 객체를 통해 검증과정을 거친다.

 

(UsernamePasswordAuthenticationToken)

 

인증 관리자 (AuthenticationManager)

interface 에서 요구하는 메소드가 authenticate 뿐이고 구현체는 또 다시 AuthenticationProvider 에게 검증을 위임하는데,

그렇다면 Manager 를 두지 않고 바로 Provider 에게 검증을 요청하면 되지않나? 라고 의문이 들었기 때문이다.

// 인증에 성공하면 두번째 생성자를 이용해 객체를 생성하여 SecurityContext에 저장한다. 

public interface AuthenticationManager {
 
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
 
}

제공 관리자(ProviderManager)

인증 관리자(AuthenticationManager)의 구현체

 

인증 제공자 (AuthenticationProvider) <<인터페이스>>

인증 관리자에 등록된 인증 제공자

인증에 대한 실질적인 처리를 한다. 

 

public interface AuthenticationProvider {
 
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
 
	boolean supports(Class<?> authentication);

정말정말정말 실제 검증을 수행하는 객체이다.

그렇다면 검증을 하기위해서는 무엇이 필요할까?

우선 클라이언트가 요청한 principal-credential 에서 principal 을 바탕으로 서버에 해당 유저 정보를 불러와야 할 것이다.

 

 

UserdetailsService

 

UserDetails 객체를 반환하는 하나의 메소드만을 가지고 있는데, 일반적으로 이를 implements한 클래스에 UserRepository를 주입받아 DB와 연결하여 처리한다.

 

UserdetailsService 은 데이터베이스에서 유저 정보를 불러와서 UserDetails 객체를 만들어 돌려주는 역할을 한다

사실 UserDetailsService 보다는 UserDetails 가 핵심인 듯 한데, 왜냐하면 UserDetails 객체를 만드는 이유가 권한(GrantedAuthority)이 부여된 객체를 생성하도록 강제하기 때문이다.

즉, Provider 는 UserDetails 구현체(User) 를 사용함으로써 해당 유저의 권한을 참조가능하게 된다.

 

UserDetailsVO - authorities 를 무조건 포함해야만 생성가능하다.

위 코드는 시스템에서 email 을 principal 로 사용하기 위해 별도의 필드로 저장한다.

 

package org.springframework.security.core.userdetails;

public interface UserDetailsService {

	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

 

UserDetails

사용자 정보(username, password 등)을 가지는 인터페이스

UserDetails를 구현하여 실제 로그인에 사용할 클래스 생성

Spring Security 내부적으로 사용하지 않고 Authentication으로 캡슐화하여 저장한다

인증에 성공하여 생성된 UserDetails를 구현한 객체는 Authentication 객체를 구현한 UsernamePasswordAuthenticationToken을 생성하기 위해 사용된다.

package org.springframework.security.core.userdetails;

public interface UserDetails extends Serializable {
	
	Collection<? extends GrantedAuthority> getAuthorities();

	String getPassword();

	String getUsername();

	boolean isAccountNonExpired();

	boolean isAccountNonLocked();

	boolean isCredentialsNonExpired();

	boolean isEnabled();

}

 

인증 (Authentication)

현재 주체의 인증 정보와 권한에 대한 것을 정의해 놓은 인터페이스

Principal, Credentials, Authorities를 정의해 놓음

 

Principal: 인증되는 주체의 ID

Credentials: 주체가 정확한지 증명

Authorities: 주체의 권한

 

UsernamePasswordAuthenticationToken 은 Token 중에서 Principal-Credential 패턴을 반영한 객체이며 가장 기본적인 Authentication 구현체다.

또한 Token 은 기본적으로 AbstractAuthenticationToken 을 상속받아 내부에 권한을 저장하는 authorities, 검증이 완료되었는지 판단하는 authenticated 상태를 저장하고있다.

흐름은 최초에 검증되지않은 Token 을 AuthenticationManager 에게 넘기면 검증 결과에 따른 새로운 Token 을 반환해준다.

 

 

Authentication 객체는 SecurityContext에 저장되며,

SecurityContextHolder를 통해 SecurityContext에 접근하고, SecurityContext를 통해 Authentication에 접근할 수 있다.

SecurityContextHolder

Security Context 영역을 관리하는 클래스

getter로 SecurityContext 객체에 접근 할 수 있으며 현재 SecurityContext를 반환함

보안 주체의 세부 정보를 포함하여 응용프로그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장된다.

package org.springframework.security.core.context;

public class SecurityContextHolder {
//... 생략
    
    public static SecurityContext getContext() {
        return strategy.getContext();
    }
    
// ...생략
}

 

SecurityContext

Authentication을 보관하는 역할을 하며 Authentication을 저장하거나 꺼내올 수 있다.

 

package org.springframework.security.core.context;

public interface SecurityContext extends Serializable {
    // Authentication에 대한 접근 getter 정의
    Authentication getAuthentication();

    void setAuthentication(Authentication authentication);

}

 

SecurityContextImpl 

Security Context 인터페이스를 구현한 객체

package org.springframework.security.core.context;

public class SecurityContextImpl implements SecurityContext {

   //...생략
   
    // Authentication 객체 get 
    @Override
    public Authentication getAuthentication() {
        return this.authentication;
    }

    @Override
    public int hashCode() {
        return ObjectUtils.nullSafeHashCode(this.authentication);
    }

    // Authentication 객체 set 
    @Override
    public void setAuthentication(Authentication authentication) {
        this.authentication = authentication;
    }

   //...생략

}

 

 

 

 

뒤에서 살펴보겠지만

유저 정보를 불러오기 위해서는 다시 UserDetailsService 구현체를 사용하고 해당 구현체는 loadUserByUsername 메소드를 통해 UserDetails 객체를 반환하도록 강제하고있다.

아무튼 유저 정보를 불러왔다면 credential 검증을 수행하면 될텐데, 보안상 데이터베이스에 저장된 비밀번호는 암호화 되어있기 때문에 클라이언트로 부터 받은 credential 또한 passwordEncoding 과정을 거친 후에 비교해야할 것이다.

주의할 점
당연하다고 생각할 수 있지만 데이터베이스 내 데이터의 encode 방식과 새로 들어온 데이터의 encode 방식은 일치해야한다.

PasswordEncoder 를 bean 으로 등록해야하는 이유

 

 

 

GrantedAuthority

GrantedAuthority는 현재 사용자(Principal)가 가지고 있는 권한을 의미하며, ROLE_ADMIN이나 ROLE_USER와 같이 ROLE_*의 형태로 사용한다. GrantedAuthority 객체는 UserDetailsService에 의해 불러올 수 있고, 특정 자원에 대한 권한이 있는지를 검사하여 접근 허용 여부를 결정한다.

 

 

 

 

 

SecurityFilterChain

HttpServletRequest에 적용되는지 여부를 결정하고 일치시킬 수 있는 필터 체인을 정의한다.

(FilterChainProxy를 구성하는 데 사용된다.)

 

필터 체인 종류

 

1) SecurityContextPersistenceFilter : SecurityContextRepository에서 SecurityContext를 가져오거나 저장 Deprecated

2) LogoutFilter : 설정된 로그아웃 URL로 오는 요청을 감시하며 인증 주체(Principal) 로그아웃 처리

3) UsernamePasswordAuthenticationFilter: 설정된 로그인 URL로 오는 요청을 감시하며 유저 인증 과정 처리

  • AuthenticationManager를 통한 인증 실행
  • 인증 성공 시, 얻은 Authentication 객체를 SecurityContext에 저장 후 AuthenticationSuccessHandler 실행
  • 인증 실패 시, AuthenticationFailureHandler 실행

4) DefaultLoginPageGeneratingFilter : 인증을 위한 로그인폼 URL을 감시

5) BasicAuthenticationFilter : HTTP 기본 인증 헤더를 감시하여 처리하고 결과를 SecurityContextHolder에 저장

6) RememberMeAuthenticationFilter: SecurityContext에 인증(Authentication) 객체가 있는지 확인하고 RememberMeServices를 구현한 객체 요청이 있을 경우 RememberMe를 인증토큰으로 SecurityContext에 주입

7) SecurityContextHolderAwareRequestFilter : HttpServletRequestWrapper를 상속한 SecurityContextHolderAwareRequestWapper 클래스로 HttpServletRequest 정보를 감싼다 SecurityContextHolderAwareRequestWrapper 클래스는 필터 체인상의 다음 필터들에게 부가정보 제공 

8) AnonymousAuthenticationFilter : 이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 인증토큰에 사용자가 익명 사용자로 나타남

9) SessionManagementFilter : 인증된 사용자와 관련된 모든 세션 추적

10) ExceptionTranslationFilter : 보호된 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달

11) FilterSecurityInterceptor : AccessDecisionManager 로 권한부여 처리를 위임함으로써 접근 제어 결정을 도움 Deprecated

12) RequestCacheAwareFilter : 로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용

 

스프링 시큐리티 사용

// 스프링시큐리티를 설정을 처리할 SecurityConfig 클래스

@Configuration
@EnableWebSecurity // WebSecurityConfigurerAdapter를 상속받는 클래스에 해당 어노테이션을 선언하면 SpringSecurityFilterChain이 자동으로 포함
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeRequests()
                .mvcMatchers("/**")
                .permitAll();
    }
}

 

 

  • void configure(HttpSecurity http): http 요청에 대한 보안을 설정합니다.
  • PasswordEncoder passwordEncoder(): 비밀번호를 그대로 저장하지 않고 BCryptPasswordEncoder의 해시 함수를 이용하여 암호화처리합니다.

 

 

 

 

https://docs.spring.io/spring-security/reference/index.html

 

Spring Security :: Spring Security

If you are ready to start securing an application see the Getting Started sections for servlet and reactive. These sections will walk you through creating your first Spring Security applications. If you want to understand how Spring Security works, you can

docs.spring.io