인가 아키텍처

enxnong·2024년 7월 13일
0

스프링 시큐리티

목록 보기
8/13

정수원님의 강의 스프링 시큐리티 완전 정복 [6.x 개정판] 보면서 공부한 내용입니다.

인가

Authorization

  • 인가(권한부여)는 특정 자원에 접근할 수 있는 사람을 결정하는 것을 말한다
  • GrantedAuthority 클래스를 통해 권한 목록을 관리하고 있으며 사용자의 Authentication 객체와 연결한다

GrantedAuthority

  • Authentication 에 GrantedAuthority 권한 목록을 저장(Collection 타입으로 저장)하며 이를 통해 인증 주체에게 부여된 권한을 사용하도록 한다
  • GrantedAuthority 객체는 AuthenticationManager 에 의해 Authentication 객체에 삽입되며 스프링 시큐리티는 인가 결정을 내릴 때 AuthorizatioinManager 를 사용하며 인증 주체로부터 GrantedAuthority 객체를 읽어들여 처리하게 된다

인가 관리자 이해

AuthorizationManager

  • 인증된 사용자가 요청 자원에 접근할 수 있는지 여부를 결정하는 인터페이스로서 인증된 사용자의 권한 정보와 요청 자원의 보안 요구 사항을
    기반으로 권한 부여 결정을 내린다
  • Spring Security 의 요청 기반, 메소드 기반의 인가 구성 요소에서 호출되며 최종 액세스 제어 결정을 수행한다
  • Spring Security의 필수 구성 요소로서 권한 부여 처리는 AuthorizationFilter 를 통해 이루어지며 AuthorizationFilter 는 AuthorizationManager 를 호출하여 권한 부여 결정을 내린다

  • check()

    • 권한 부여 결정을 내릴 때 필요한 모든 관련 정보(인증객체, 체크 대상(권한정보, 요청정보, 호출정보 등..)가 전달된다
    • 액세스가 허용(접근 허용)되면 true 를 포함하는 AuthorizationDecision 객체 반환, 거부되면 false 를 포함하는 AuthorizationDecision 객체 반환, 결정을 내릴 수 없는 경우 null을 반환한다
  • verify()

    • check 를 호출해서 반환된 값이 false 가진 AuthorizationDecision 인 경우(즉, 거부당한 경우) AccessDeniedException을 throw 한다

AuthorizationManager 클래스 계층 구조

  • RequestMatcherDelegatingAuthorizationManager : 요청 기반 권한 부여 관리자로 클라이언트의 요청에 대해 적합한 구현체를 선택하여 권한 심사를 위임한다
    • AuthenticatedAuthorizationManager : 인증된 사용자에게 접근 허용, 인증 받지 못하면 접근 허용 불가
    • AuthorityAuthorizationManager : 특정 권한을 가진 사용자만 접근 허용(가장 많이 사용됨)
    • WebExpressionAuthorizationManager : 웹 보안 표현식을 사용하여 권한 관리
  • 메서드 기반 권한 부여 관리자 : 요청 기반과 달리 AOP로 되어있어서 Interceptor, pointcut, advice 같은 클래스들이 연계되어 사용된다
    • PreAuthorizeAuthorizationManager : @PreAuthorize 어노테이션과 함께 사용되며 메소드 실행 전에 사용자의 권한을 확인한다
    • PostAuthorizeAuthorizationManager : @PostAuthorize 어노테이션과 함께 사용되며, 메소드 실행 후 결과에 따라 접근을 허용하거나 거부한다
    • Jsr250AuthorizationManager : JSR-250 어노테이션(@RolesAllowed, @DenyAll, @PermitAll)을 사용하여 권한을 관리한다
    • SecuredAuthorizationManager : @Secured 어노테이션을 사용하여 메소드 수준의 보안을 제공한다. 이 어노테이션은 특정 권한을 가진 사용자만 메소드에 접근할 수 있게 한다

💡 Method Security를 시작하려면 @Configuration이 있는 곳에 @EnableMethodSecurity를 추가하면 된다.

@EnableMethodSecurity
@Configuration
public class SecurityConfig {
  public SecurityFilterChain config(HttpSecurity http) throws Exception{
  	return http.build();
  }
}

요청 기반 인가 관리자

AuthorityAuthorizationManager 외 클래스 구조 이해

  • 스프링 시큐리티는 요청 기반의 인증된 사용자 및 특정권한을 가진 사용자의 자원접근 허용여부를 결정하는 인가 관리자 클래스들을 제공한다
    • AuthorityAuthorizationManager, AuthenticatedAuthorizationManager 와 대리자인 RequestMatcherDelegatingAuthorizationManager

스프링 시큐리티 인가처리

http.authorizeHttpRequests(auth -> auth
	.requestMatchers("/user").hasRole("USER") 
    // 시큐리티 내부에서 저장하는 정보

AuthenticatedAuthorizationManager

  • 인증 상태에 따라 접근 허용 / 허용불가로 나뉜다
  • 내부적으로 네 개의 AbstractAuthorizationStrategy 구현을 통해 인증 클라이언트 요청에 맞는 인증 여부 전략을 선택한다
    • FullyAuthenticatedAuthorizationStrategy : 익명 인증 및 기억하기 인증이 아닌지 검사
    • AuthenticatedAuthorizationStrategy : 인증된 사용자인지 검사
    • RememberMeAuthorizationStrategy : 기억하기 인증인지 검사
    • AnonymousAuthorizationStrategy : 익명 사용자인지 검사
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) {
 http.authorizeHttpRequests(auth -> auth
        .requestMatchers("/user").authenticated()
        .requestMatchers("/myPage").fullyAuthenticated()
        .requestMatchers("/guest").anonymous()
        .requestMatchers("/history").rememberMe());
  return http.build();
}

요청 기반 Custom AuthorizationManager 구현

public class CustomAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    private static final String REQUIRED_ROLE = "ROLE_SECURE";

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {

        Authentication auth= authentication.get();

        if(auth == null || !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken){
            // 인증 정보 없거나 인증을 받지 못했거나 익명사용자인 경우
            return new AuthorizationDecision(false);
        }

        // 권한 요청 중 하나라도 일치하는지 확인
        boolean hasRequireRole = auth.getAuthorities().stream()
                .anyMatch(grantedAuthority -> REQUIRED_ROLE.equals(grantedAuthority.getAuthority()));

        return new AuthorizationDecision(hasRequireRole);

    }
}

// SecurityConfig Class
@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/user").hasRole("USER")
                        .requestMatchers("/db").access(new WebExpressionAuthorizationManager("hasRole('DB')"))
                        .requestMatchers("/admin").hasAuthority("ROLE_ADMIN")
                        .requestMatchers("/api").access(new CustomAuthorizationManager())
                        .anyRequest().authenticated())
                .formLogin(Customizer.withDefaults())
                .csrf(AbstractHttpConfigurer::disable);

        return http.build();
    }

RequestMatcherDelegatingAuthorizationManager 로 인가 설정 응용하기

  • RequestMatcherDelegatingAuthorizationManager 의 mappings 속성에 직접 RequestMatcherEntry 객체를 생성하고 추가한다

  • getEntry() : 요청 패턴에 매핑된 AuthorizationManager 객체를 반환한다
  • getRequestMatcher() : 요청 패턴을 저장한 RequestMatcher 객체를 반환한다

메서드 기반 인가 관리자

PreAuthorizeAuthorizationManager 외 클래스 구조 이해

  • 스프링 시큐리티는 메서드 기반의 인증된 사용자 및 특정권한을 가진 사용자의 자원접근 허용여부를 결정하는 인가 관리자 클래스들을 제공한다
    • PreAuthorizeAuthorizationManager, PostAuthorizeAuthorizationManager, Jsr250AuthorizationManager, SecuredAuthorizationManager 등
  • 메서드 기반 권한 부여는 내부적으로 AOP 방식에 의해 초기화 설정이 이루어지며(요청 기반과 가장 큰 차이점) 메서드의 호출을 MethodInterceptor 가 가로 채어 처리하고 있다

💡 요청 기반은 필터에서 여러가지 빈 또는 클래스를 만들어 초기화 설정을 이룬다

메서드 권한 부여 초기화 과정

  1. 초기화 시 생성되는 빈을 검사하면서 보안이 설정된 메소드가 있는지 탐색한다
    ex) public List<User> user()
  2. 보안이 설정된 메소드를 탐색 후 그 빈의 프록시 객체를 자동으로 생성한다
    UserService 빈의 대리자 즉, 가짜 객체를 생성
  3. 보안이 설정된 메소드에는 인가처리 기능을 하는 Advice를 등록한다
    MethodInterceptor Advice 등록
  4. 빈 참조 시 실제 빈이 아닌 프록시로 생성된 빈 객체를 참조하도록 처리한다
    UserService의 프록시 객체가 참조된다
  5. 초기화 과정이 종료된다
  6. 사용자는 프록시 객체를 통해 메소드를 호출하게 되고, 프록시 객체는 Advice가 등록된 메서드가 있다면 호출하여 Advice를 작동 시킨다
  7. Advice 는 메소드 진입 전 인가 처리를 하게 되고(@PreAuthorize로 설정했기 때문에) 인가처리가 승인되면 실제 객체의 메소드를 호출하게 되고 인가처리가 거부되면 예외가 발생하고 메소드 진입이 실패한다

💡 Advice

  • 프록시가 호출하는 부가 기능으로 프록시 로직이라고 생각하면 된다

MethodInterceptor 의 종류

  • AuthorizationManagerBeforeMethodInterceptor
    • 메서드 진입 전 권한 심사를 평가
    • @PreAuthorize, @Secured, @PermitAll에 설정된 권한을 평가하는 클래스
  • AuthorizationManagerAfterMethodInterceptor
    • 메서드 진입 후 반환 결과에 따라 접근 가능 여부 결정
    • @PostAuthorize에 설정된 권한을 평가하는 클래스
  • PreFilterAuthorizationMethodInterceptor
    • @PreFilter 어노테이션에서 표현식을 평가하여 메소드 인자를 필터링 하는 구현체
  • PostFilterAuthorizationMethodInterceptor
    • @PostFilter 어노테이션에서 표현식을 평가하여 보안 메서드에서 반환된 객체를 필터링 하는 구현체

@PreAuthorize 처리 구조 이해

  • 인증이 승인되면 실제 빈의 user()가 호출된다

@PreAuthorize 처리 구조 이해

  • user()를 먼저 호출하여 return 받은 값으로 인증 승인 여부를 확인한다

메서드 기반 Custom AuthorizationManager 구현

  • 사용자 정의 AuthorizationManager를 생성하여 메서드 보안을 구현할 수 있다
  • 설정 클래스 정의
@EnableMethodSecurity(prePostEnabled = false) 
@Configuration
public class MethodSecurityConfig {

  @Bean
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public Advisor preAuthorize() { // 메소드 진입 전 권한 심사하는 클래스
      return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(new MyPreAuthorizationManager());
  }

  @Bean
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public Advisor postAuthorize() { // 메소드 진입 후 권한 심사하는 클래스
      return AuthorizationManagerAfterMethodInterceptor.postAuthorize(new MyPostAuthorizationManager());
  }
}

포인트 컷 메서드 보안 구현하기

AspectJExpressionPointcut / ComposablePointcut

  • 메서드 보안은 AOP 를 기반으로 구축되었기 때문에 어노테이션이 아닌 패턴 형태로 권한 규칙을 선언할 수 있다
  • 자체 어드바이저를 발행하거나 포인트컷을 사용하여 메서드만 선언해도 어노테이션을 사용한 것 처럼 설정할 수 있다
    • @PreAuthorize 어노테이션을 설정해야지 메서드 보안 규칙이 이루어지는데 위 어노테이션을 사용하지 않고 메서드만 선언해도 된다
  • 빈 정의하기
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor protectServicePointcut() {
  AspectJExpressionPointcut pattern = new AspectJExpressionPointcut();
  pattern.setExpression("execution(* io.security.MyService.user(..))"); // 권한 규칙이 실행되어야되는 메서드 설정
  // 반환 타입 상관없이 io.security안에 있는 MyService클래스의 user메서드를 호출할 때 권한 규칙을 작동해라
  // 해당 설정을 통해 스프링 초기화가 실행됨
  manager = AuthorityAuthorizationManager.hasRole("USER"); 
  // 권한 규칙을 실행하는 매니저
  // 권한 = user
  return new AuthorizationManagerBeforeMethodInterceptor(pattern, manager);
  // 메서드 호출 진입 전 interceptor가 작동
}

AOP 메서드 보안 구현하기

MethodInterceptor, Pointcut, Advisor

  • MethodInterceptor, Pointcut, Advisor, AuthorizationManager 등을 커스텀하게 생성하여 AOP 메서드 보안을 구현 할 수 있다

AOP 요소 이해

  • Advisor
    • AOP Advice 와 Advice 적용 가능성을 결정하는 포인트컷를 가진 기본 인터페이스이다
    • 스프링이 편하게 관리하기 위해 도입한 개념이다
  • MethodInterceptor(Advice)
    • 대상 객체를 호출하기 전과 후에 추가 작업을 수행하기 위한 인터페이스로서 수행 이후 실제 대상 객체의 실제 객체의 대상(메서드)을 호출하기 위해 Joinpoint를 통해서 호출한다

💡 Joinpoint

  • 클래스나 필드 또는 메서드에 어드바이스를 적용할 수 있는 대상 자체를 의미한다
  • Spring AOP에서 Joinpoint는 항상 메서드의 실행을 의미한다. 즉, 메서드 레벨 중에 AOP 적용할 수 있는 지점으로 Aspect Code 추가하면 공통기능이 해당 지점 전/후에 자동으로 추가되어 실행이 된다.
  • Pointcut
    • AOP 에서 Advice 가 적용될 메소드나 클래스를 정의하는 것으로서 어드바이스가 실행되어야 하는 '적용 지점'이나 '조건'을 지정한다
    • 즉, 조인포인트(Join Point)에서 어드바이스(Advice)가 적용될 위치를 선별하는 기능

AOP 초기화

  • 스프링은 초기화되면 실제로 AOP가 적용되어야되는 클래스의 메서드들을 찾아서 해당 어드바이스를 등록시킨다
  • 이를 통해 런타임 환경에서 해당 메서드들을 호출할 때 AOP에 등록된 어드바이스가 호출되면서 여러 작업을 실행한다
  • AnnotationAwareAspectJAutoProxyCreator 클래스가 초기화 과정에서 자동으로 프록시 객체를 생성한다
    • 즉, AOP가 적용되어야할 어노테이션을 전체적으로 찾고, 어드바이저들을 빈으로 정의하면 AOP가 적용될 수 있도록 어드바이저들을 등록하는 역할을 한다

AOP 메서드 보안 구현

  • Advice 구현
  • MethodInterceptor가 Advice를 상속하기 때문에 Advice를 구현하고 있는 것으로 볼 수 있다


    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public MethodInterceptor methodInterceptor() {

        // 현재 메서드를 호출한 사용자의 인증 상태만 보겠다
        AuthorizationManager<MethodInvocation> authorizationManager = new AuthenticatedAuthorizationManager<>();
        // CustomMethodInterceptor에 생성자로 전달
        return new CustomMethodInterceptor(authorizationManager);

    }
  • 포인트컷 생성
  • 어떤 클래스, 메소드에 Advice를 적용할 것인가를 결정하는 기준
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public Pointcut pointcut() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // 어떤 메소드에 권한 규칙을 설정할 것 인가
        pointcut.setExpression("execution(* io.security.springsecuritymaster.chapter9.DataService.*(..))");
        return pointcut;
    }
  • Advisor 구현
  • 포인트컷과 어드바이스를 담음
    @Bean
    public Advisor serviceAdvisor(){
        return new DefaultPointcutAdvisor(pointcut(),methodInterceptor());
    }
profile
높은 곳을 향해서

0개의 댓글