JAVA/SPRING

[SPRING SECURITY] 1012 공부(아키텍쳐 마지막 정리.)

gracelove91 2019. 10. 12. 23:26

Authentication과 SecurityContextHolder

위에서 AuthenticationManager가 인증을 마친 뒤 Authentication 리턴해준다고했다.

그렇다면 리턴된 Authentication은 어디로 가는걸까?

  • UsernamePasswordAuthenticationFilter
    • 폼인증을 처리하는 시큐리티필터.
    • 인증된 Authentication 객체를 SecurityContextHolder에 넣어주는 필터.
      • SecurityContextHolder.getContext().setAuthentication(authentication)

Filter와 FilterChainProxy.

FilterChainProxy가 Filter들을(UsernamePasswordAuthenticationFilter 등등) 호출한다.

우리가 정의한 SecurityConfig이 FilterChinaProxy의 FilterChain(타입은 어레이리스트)이 된다.

(SecurityConfig 두 개 정의하면 FilterChain size가 2다.)

DelegatingFilterProxy와 FilterChainProxy

어떠한 요청을 보내면 서블릿 컨테이너(톰캣)가 받는다.

서블릿 컨테이너는 서블릿스펙을 지원한다.

스펙에는 filter가 있는데, 요청의 앞 뒤로 특수한 행위를 할 수 있다 인터셉터와 비슷.

delegatingfilterProxy

위임을 하는 필터 프록시. 누구에게?

스프링 컨테이너에 들어있는 특정한 빈에게.

자기가 해야할 일을 위임한다.

어떤 빈에다 위임할 지 구체적인 명시가 필요하다 (빈의 이름으로)

AbstractSecurityWebApplicationInitializer을 사용해서 명시. 또는 web.xml에 명시.

부트를 쓴다면 자동으로 등록된다. (SecurityFilterAutoConfiguration)

스프링 시큐리티 관점에서는

delegatingFilterProxy는 FilterChainProxy에 위임한다.

위임을 하기 위해선 빈의 이름으로 구체적인 명시를 해야한다.

FilterChainProxy의 이름은 보통 SpringSecurityFilterChain다.

(SecurityFilterAutoConfiguration -> DEFAULT_FILTER_NAME 상수에 있다 )

인증은 했는데 인가는 어떻게 하는거지?


AccessDecisionManager

AccessControll 결정을 내리는 인터페이스. 구현체 3가지를 기본으로 제공.

  • AffirmativeBased : 여러 Voter 중 하나라도 허용하면 허용. 기본전략이다

    (디버거 걸어서 확인해보자 AffirmativeBased.java)

  • ConsensusBased : 다수결

  • UnanimousBased : 만장일치

리소스에 접근할 때 허용할 것인가? 를 확인함.

인증을 할때는 AuthenticationManager.

인가를 할때는 AccessDecisionManager.

여러개의 Voter를 가질 수 있다.

  • 해당 Autentication이 특정한 Object에 접근할 때 필요한 ConfigAttributes를 만족하는지 확인(ROLE_USER 등)

커스터마이징 하는 법

  1. UserDetailsService에서 리턴하는 UserDetails 에서 권한 두개 주기.

    return User.builder()
      .username(account.getUsername())
      .password(account.getPassword())
      .roles(account.getRole, "USER")
      .build()
  1. 계층형 구조

    SecurityConfig.java

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
      private AccessDecisionManager accessDecisionManager() {
            RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
            roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
    
            DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
            handler.setRoleHierarchy(roleHierarchy);
    
            WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
            webExpressionVoter.setExpressionHandler(handler);
    
            List<AccessDecisionVoter<? extends Object>> voters = Arrays.asList(webExpressionVoter);
            AccessDecisionManager accessDecisionManager = new AffirmativeBased(voters);
    
            return accessDecisionManager;
        }
    
            @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http
                        .authorizeRequests()
                        .mvcMatchers("/","/info","/account/**").permitAll()
                        .mvcMatchers("/admin").hasRole("admin")
                        .mvcMatchers("/user").hasRole("USER")
                        .anyRequest().authenticated()
                        .accessDecisionManager(accessDecisionManager());
            http.formLogin();
            http.httpBasic();
    
        }
    
    

}


   또는

   ````java
   @Configuration
   @EnableWebSecurity
   public class SecurityConfig extends WebSecurityConfigurerAdapter {

       public SecurityExpressionHandler expressionHandler() {
           RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
           roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");

           DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
           handler.setRoleHierarchy(roleHierarchy);

           return handler;
       }

       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.authorizeRequests()
                   .mvcMatchers("/", "/info", "/account/**").permitAll()
                   .mvcMatchers("/admin").hasRole("ADMIN")
                   .mvcMatchers("/user").hasRole("USER")
                   .anyRequest().authenticated()
                   .expressionHandler(expressionHandler());
           http.formLogin();
           http.httpBasic();
       }

   }

그렇다면 AccessDecisionManager는 어디서 사용할까

FilterSecurityInterceptor

역시 FiterChainProxy가 들고있는 것 중 하나다.

인증을 다 거친 뒤 AccessDecisionManager를 사용하여 마지막으로 AccessControl(인가)을 한다.

AbstranctSecurityInterceptor.java 디버거 걸어보자.

ExceptionTranslationFilter

필터체인에서 발생하는 AccessDeniendException과 AuthentcationException을 처리하는 필터

AuthenticationException 발생시

  • AuthenticationEntryPoint 실행.
  • AbstractSecurityInterceptor 하위 클래스 ( ex . FilterSecurityInterceptor ) 에서 발생하는 예외만 처리한다.
  • 그렇다면 UsernamePasswordAuthenticationFilter에서 발생한 인증 에러는 ? (로그인할 때 일어나는 익셉션)
    • 상위 클래스인 AbstractAuthenticationProcessingFilter에서 처리한다.

AccessDeniedException 발생시

  • 익명사용자라면 AuthenticationEntryPoint 실행.(form인 경우 로그인페이지로 이동할 것.)
  • 익명 사용자가 아니라면 AccessDeniedHandler에게 위임.

정리

서블릿 컨테이너에 요청이 들어온다.

서블릿 필터 중 델리게이팅필터프록시(부트 쓸 땐 자동 설정, 레거시는 AbstractSecurityWebApplicationInitilizer를 사용해서 등록하자) 가 요청을 받는다.

델리게이팅필터프록시는 스프링의 필터체인프록시에 위임한다.(위임할 땐 빈의 이름을 명시적으로 설정해야하는데, 보통 springSecurityFilterChin이란 이름으로 등록된다.)

필터체인프록시는 필터체인을 가지고 있다

필터체인프록시는 WebSecurity와 HttpSecurity로 만들어진다.(WebSecurityConfigureAdapter)

인증 관련해서는 AuthenticationManager를 사용한다

  • 구현체로는 ProviderManager를 사용한다.
    • 내부적으로 여러 AuthenticationProvider를 사용한다.
      • 그 중하나가 DaoAuthenticationProvider다.
        • UserDetailsService를 사용한다.
    • 인증이 성공한다면 SecurityContextHolder에 set 해준다.

인가 관련해서는 AcessDecisionManager를 사용한다.

  • SecurityContextHolder에 저장돼있는 Authentication이 접근하는 리소스에 적절한 configAttributes(ROLE)을 가지고있는지 확인한다.
    • 확인하는 방법은 세가지가 있는데 AffrimativeBased를 기본전략으로 사용한다.
      • 여러 AccessDesionVoter를 가지고 있다
        • 그 중 하나가 WebExpressVoter다
          • SecurityExpressionHandler