[스프링시큐리티] 인가 아키텍처

Welcome to Seoyun Dev Log·2024년 12월 17일
1

보안

목록 보기
11/18

Authorization

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

  • 스프링 시큐리티는 Authentication 에 GrantedAuthority 권한 목록을 저장하며 이를 통해 인증 주체에게 부여된 권한을 사용하도록 한다

권한 관련된 내용들이 문자열로 저장되어있다
SimpleGrantedAuthority 생성시 접두사를 사용하여 ROLE_USER 등의 권한으로 저장되어 Collection에 담기고
Authentication 인증 객체안에 collection으로 GrantedAuthority가 저장된다

접두사 커스텀

스프링 시큐리티는 ROLE_접두사를 기본적으로 사용하나
커스텀하여 사용이 가능하다

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() { 
		return new GrantedAuthorityDefaults("MYPREFIX_");
}

//hasRole("ADMIN") => ROLE_ADMIN
hasRole("ADMIN") => MYPREFIX_ADM//IN

AuthorizationManager 인가관리자

verify()는 디폴트로 check()으로 구현된 메서드를 호출한다


요청기반 인가 관리자

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

스프링 시큐리티 인가처리

  • 스프링시큐리티가 내부에 가지고 있는 정보
  • client에게 받아야 하는 정보

위 둘을 권한 심사하는 것

⭐️내부 구조

  1. 클라이언트 요청
  2. 요청에 대한 인가처리 authorizationFilter
  3. 필터가 요청을 받고 securityContextHolder로 보내서 인증 객체를 가져온다
  4. request 요청정보와 인증객체(권한 정보를가지고 있다)를 authorizatinoManager(인가관리자)에 보낸다
  5. 적절한 인가 관리자를 배정(위임)하는것
    AuthorizationManager에서 requestMatcher를 통해 URL을 찾을 때 목록의 순회를 돌아서 if문으로 해당 조건이 있는지 확인하는 과정을 거친다
  6. 요청자원 접근에 대한 최종 승인 혹은 거부 결과 반환
  7. 인증방식에 따라 적절한 Strategy를 선택해서 인증 여부 확인

AuthenticatedAuthorizationManager 흐름도 / 구조

AuthorityAuthorizationManager

  • 매핑은 EndPoint 기준으로 mapping된다 따라서 requestMatchers가 4개라도 엔드포인트가 5개이면 5개 매핑


요청 기반 Custom_AuthorizationManager 구현 하여 권한 심사하기

  • 스프링 시큐리티 인가 설정 시 선언적 방식이 아닌 프로그래밍 방식으로 구현할 수 있으며 access(AuthorizationManager) API 를 사용한다
  • access() 에는 AuthorizationManager< RequestAuthorizationContext > 타입의 객체를 전달할 수 있으며 사용자의 요청에 대한 권한 검사를 access()
    에 지정한 AuthorizationManager 가 처리하게 된다
  • access() 에 지정한 AuthorizationManager 객체는 RequestMatcherDelegatingAuthorizationManager 의 매핑 속성에 저장된다
http.authorizeHttpRequests(auth -> auth.requestMatcher().access(AuthorizationManager)
  1. 특정한 엔드포인트에 대한 권한 검사를 수행하기 위해 AuthorizationManager 를 구현하여 설정한다
  2. "/user", "/myPage", "/admin" 요청 패턴의 권한 검사는 AuthorityAuthorizationManager 가 처리한다
    "/api“ 요청 패턴의 권한 검사는 CustomAuthorizationManager 가 처리
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);
  }
  // "ROLE_SECURE" 권한을 가진 사용자인지 확인
  boolean hasRequiredRole = auth
    .getAuthorities()
    .stream()
    .anyMatch(grantedAuthority -> REQUIRED_ROLE.equals(grantedAuthority.getAuthority())
  );
  
  return new AuthorizationDecision(hasRequiredRole); 
  }
}

RequestMatcherDelegatingAuthorizationManager 인가 설정 응용하기

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

요청 패턴에 매핑된 AuthorizationManager 객체를 반환
요청 패턴을 저장한 RequestMatcher 객체를 반환

사용

http.authorizeHttpRequests(auth -> auth
.anyRequest().access(new CustomRequestMatcherDelegatingAuthorizationManager())

모든 요청에 적용하기 때문에 url을 따로 지정하지 않아도 된다

1.RequestMatcherDelegatingAuthorizationManager 내부적으로 무조건 생성되고 mappings에 add함 이것은 변경할 수 없는 필수 절차이다.
2. 1번에 생성된 manager가 custom으로 만든 manager안에 들어가 있는 것이다.
3. 2번 객체안에 또 RequestMatcherDelegatingAuthorizationManager를 넣었지만 이 구조는 테스트용 구조로 좋은 구조는 아니다. 원리를 알기위해 학습용 구현

  • springSecurityConfig
//하드코딩으로 수동적인 방법

@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

return http.authorizeHttpRequests(auth -> auth
  .anyRequest()
  .access(authorizationManager(null)))
  .build();
}

@Bean
public AuthorizationManager<RequestAuthorizationContext> authorizationManager(HandlerMappingIntrospector introspector){ 
  List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings = new ArrayList<>();

  RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> requestMatcherEntry1 =
  new RequestMatcherEntry<>(new MvcRequestMatcher(introspector, "/user"), AuthorityAuthorizationManager.hasAuthority("ROLE_USER"));

  RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> requestMatcherEntry2 =
  new RequestMatcherEntry<>(new MvcRequestMatcher(introspector, "/admin"), AuthorityAuthorizationManager.hasRole("ADMIN"));

  RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> requestMatcherEntry3 =
  new RequestMatcherEntry<>(AnyRequestMatcher.INSTANCE, new AuthenticatedAuthorizationManager<>());

  mappings.add(requestMatcherEntry1);
  mappings.add(requestMatcherEntry2);
  mappings.add(requestMatcherEntry3);

  return new CustomRequestMatcherDelegatingAuthorizationManager(mappings);
}
public class CustomRequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> { 

private final RequestMatcherDelegatingAuthorizationManager manager;

  public CustomRequestMatcherDelegatingAuthorizationManager(List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings) { 
    Assert.notEmpty(mappings, "mappings cannot be empty");
    manager = RequestMatcherDelegatingAuthorizationManager.builder()
      .mappings(maps -> maps.addAll(mappings))
      .build();
    }

  @Override
  public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) { 
  //단순 manager에 위임하는 일
  //TODO: 중복되는 위임 과정이 존재하기 때문에 개선 필요
      return manager.check(authentication,object.getRequest());
  }

  @Override
  public void verify(Supplier<Authentication> authentication, RequestAuthorizationContext object) { 
      AuthorizationManager.super.verify(authentication, object);
  } 

}
  • 요청에 대한 권한 검사를 RequestMatcherDelegatingAuthorizationManager 객체가 수행하도록 한다
    • RequestMatcherDelegatingAuthorizationManager > CustomRequestMatcherDelegatingAuthorizationManager >
    RequestMatcherDelegatingAuthorizationManager 구조는 조금 더 개선이 필요하다

메서드 기반 인가 관리자

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

  • 권한에 따른 메서드가 매핑이되어 스프링시큐리티에 저장된다
@PreAuthorize("hasAuthority('ROLE_USER')") 
public List<User> users() {

	System.out.println("users: " + UserRepositiry.findAll()); 
}
  • 메서드 호출자의 관점

위 두가지를 비교하여 인가 처리 진행하는 것

⭐️초기화 과정 (AOP)

📌보안이 설정된 메소드가 있는 경우 그 빈의 프록시 객체를 자동으로 생성하는것이고
없다면 생성되지않는다 생성된 proxy 객체로 어떠한 작업을 진행하기 때문.



메서드 기반 Custom AuthorizationManager 구현

  • 사용자 정의 AuthorizationManager 를 생성함으로 메서드 보안을 구현할 수 있다
@EnableMethodSecurity(prePostEnabled=false) 
//클래스 단위에 적용. 
//시큐리티가 제공하는 클래스들을 비활성화한다. 그렇지않으면 중복해서 검사하게 된다
//메서드에 어노테이션 사용
@Bean 
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) 
public Advisor preAuthorize() {

    return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(new MyPreAuthorizationManager()); 

}

포인트 컷 메서드 보안 구현

위의 메서드 보안 기반 권한 규칙을 어노테이션 사용을 하지않고 선언할 수 있다

  • 메서드 보안은 AOP를 기반으로 구축되었기 때문에 어노테이션이 아닌 패턴 형태권한 규칙을 선언할수있으며 이는 요청 수준의 인가와 유사한 방식이다
  • 자체 어드바이저(Advisor)를 발행하거나 포인트컷(PointCut)을 사용하여 AOP 표현식을 애플리케이션의 인가 규칙에 맞게 매칭할 수 있으며 이를 통해 어노테이션을 사용하지 않고도 메소드 수준에서 보안 정책을 구현할 수 있다

📌 AspectJExpressionPointcut을 사용하기 위해서
의존성 추가해야 한다.

implementation 'org.springframework.boot:spring-boot-starter-aop:3.4.0'

여러가지 표현식이 존재한다.

포인트컷 사용의 장점
유연한 적용 범위 지정:
애너테이션 기반 접근 제어(@PreAuthorize 등)는 주로 특정 메소드나 클래스에 직접 부착하는 방식으로 접근 제어를 수행한다. 반면 포인트컷을 사용하면 메소드 시그니처(메소드명 패턴), 패키지 구조, 파라미터 타입 등 다양한 기준을 통해 원하는 범위에 접근 제어를 유연하게 적용할 수 있다.

비침투적(Non-invasive) 접근 제어:
포인트컷을 사용하면 대상 코드(비즈니스 로직)에는 별다른 주석이나 어노테이션을 추가하지 않고도 권한 검사를 적용할 수 있다. 즉, 보안 로직이 애플리케이션 로직에 덜 침투적이며, 관심사의 분리가 더욱 명확해진다.

재사용성과 유지보수성 향상:
특정 패키지 혹은 특정 네이밍 컨벤션에 따라 접근 제어를 일괄 적용하거나 변경할 때, 포인트컷 정의만 수정하면 전체 적용 대상에 영향을 줄 수 있다. 이를 통해 재사용성과 유지보수성을 높일 수 있다.

복잡한 규칙 적용 용이:
애너테이션만으로 처리하기 애매한 복잡한 권한 규칙(예: 특정 파라미터 값 조건, 특정 리턴 타입에 따른 접근 제어 등)도 AOP 포인트컷 표현식과 어드바이스 로직을 조합하면 유연하게 처리할 수 있다.


참고

만약 PRE_FILTER와 PRE_AUTHORIZE 둘다 설정되어있는 경우 PRE_FILTER가 먼저 수행된다.

profile
하루 일지 보단 행동 고찰 과정에 대한 개발 블로그

0개의 댓글

관련 채용 정보