Search
Duplicate

실전프로젝트 - 인가 프로세스 DB연동 서비스계층 구현

Method방식 개요

1. 서비스 계층의 인가 처리 방식

화면, 메뉴 단위가 아닌 기능 단위로 인가처리
메소드 처리 전, 후로 보안 검사 수행하여 인가처리

2. AOP기반으로 동작

프록시와 어드바이스로 메소드 인가처리 수행

3. 보안 설정 방식

어노테이션 권한 설정 방식
@PreAuthorize("hasRole('USER')"), @PostAuthorize("hasRole('USER')"), @Secured("ROLE_USER")
Java
복사
맵 기반 권한 설정 방식
맵 기반 방식으로 외부와 연동하요 메소드 보안 설정 구현
참고: AOP란?

AOP Method 기반 DB 연동 - 주요 아키텍처 이해

인가 처리를 위한 초기화 과정과 진행

초기화 과정
1.
초기화 시 전체 빈을 검사하면서 보안이 설정된 메소드가 있는지 탐색
2.
빈의 프록시 개체를 생성
3.
보안 메소드에 인가처리(권한심사) 기능을 하는 Advice 를 등록
4.
빈 참조시 실제 빈이 아닌 프록시 빈 객체를 참조
진행 과정
1.
메소드 호출 시 프록시 객체를 통해 메소드를 호출
2.
Advice가 등록되어 있다면 Advice 를 작동하게 하여 인가 처리
3.
권한 심사 통과하면 실제 빈의 메소드를 호출한다.

인가 처리를 위한 초기화 과정

스프링 초기화 시 '빈 후 처리기 클래스'에서 전체 빈(Bean)을 검사하는데, 이 클래스의 구현체 중 하나가 자동 프록시 생성기(DefaultAdvisorAutoProxyCreator)입니다.
자동 프록시 생성기는 빈들을 검사하면서 보안이 설정된 메소드를 찾아서 그 메소드를 가지고 있는 빈이 있으면 그 빈의 프록시객체를 생성합니다.
위 그림은 자동 프록시 생성기가 어떤 과정을 거쳐서 보안이 설정된 빈을 찾아서 프록시 객체를 만드는지에 대한 과정을 도식화 한 것입니다.
자동 프록시 생성기(DefaultAdvisorAutoProxyCreator)는 MethodSecurityMetadataSourceAdvisor를 통해서 빈(Bean)을 검사한다.
MethodSecurityMetadataSourceAdvisorMethodSecurityMetadataSourcePointcutMethodSecurityInterceptor 클래스를 가지고 있다.
MethodSecurityMetadataSourcePointcut
⇒ 메소드에 보안이 설정되있는지 탐색하는 클래스이고, 해당 클래스에서 현재 검사하는 빈(Bean)의 클래스 및 메소드 정보를 MethodSecurityMetadataSource클래스에 전달합니다. 그럼 MethodSecurityMetadataSource는 전달받은 정보를 파싱(parse())해서 권한설정 어노테이션등을 찾습니다.
MethodSecurityInterceptor
MethodSecurityMetadataSource 에서 보안설정이 된 메서드를 찾았을 경우 MethodSecurityInterceptor 에 어드바이스 등록을 하는 클래스
보안 메소드가 설정된 빈일 경우 프록시 생성대상이 되고 자동프록시 생성기에서는 프록시 객체를 생성합니다.
(important) 프록시 객체가 생성 된 다음MethodSecurityInterceptor 에 어드바이스가 등록됩니다.

초기화 과정 이후 로직

사용자가 order()display() 메서드를 호출 합니다.
order() 호출
1.
OrderServiceProxy 객체에서 요청을 받아서 Advice등록이 되어있는지 확인합니다.
2.
MethodSecurityInterceptor에서 인가처리를 합니다.
3.
승인되었을 경우 실제 객체(orderService) 에서 order() 메서드를 호출합니다.
4.
거절되었을 경우 예외(AccessDeniedException)를 발생합니다.
display() 호출
1.
OrderServiceProxy 객체에서 요청을 받아서 Advice등록이 되어있는지 확인합니다.
2.
display()Advice 등록이 되어있지 않기 때문에 바로 실제객체 메소드(orderService.display())를 호출합니다.

어노테이션 권한 설정

@PreAuthorize, @PostAuthorize, @Secured, @RolesAllowed

보안이 필요한 메서드에 설정한다.

1. @PreAuthorize, @PostAuthorize

SpEL 지원
@PreAuthroize("hasRole('ROLE_USER') and (#account.username == principal.username)")
PrePostAnnotationSecuritymetadataSource가 담당
Code
package io.security.corespringsecurity.aopsecurity; import io.security.corespringsecurity.domain.dto.AccountDto; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import java.security.Principal; @Controller public class AopSecurityController { @GetMapping("/preAuthorize") @PreAuthorize(value = "hasRole('ROLE_USER') AND (#accountDto.username == principal.username)") public String preAuthorize(AccountDto accountDto, Model model, Principal principal){ model.addAttribute("method", "Success @PreAuthorize"); return "aop/method"; } }
Java
복사

2. @Secured, @RolesAllowed

SpEL 미지원
@Secured("ROLE_USER"), @RolesAllowed("ROLE_USER")
SecuredAnnotationSecurityMetadataSource, Jsr250MethodSecurityMetadataSource가 담당
Code
@Override @Secured("ROLE_MANAGER") public void order() { System.out.println("order"); }
Java
복사

3. @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

해당 어노테이션을 통해 설정을 해줘야 , 항목 어노테이션이 활성화가 된다.
Code
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) @Slf4j @Order(1) public class SecurityConfig extends WebSecurityConfigurerAdapter { ... }
Java
복사

AOP Method 기반 DB 연동

MapBasedSecurityMetadataSource(1)

Method 방식 - Map 기반 DB 연동

Filter 기반 Url방식과 AOP기반 Method 방식의 구조는 몹시 유사합니다.
양 쪽중 어느 방식으로 하던 동일한 구조로 동작합니다.
Filter기반 Url 방식은 FilterSecurityInterCeptor 필터가 동작해서 인가처리를 하지만, AOP기반 Method방식은 MethodSecurityInterceptor라는 인터셉터에서 어드바이스가 작동해서 처리가 됩니다. 즉, 필터기반은 요청을 가로채 동작하고, Method방식은 메소드 호출시 프록시 객체가 메소드에 등록된 Advice를 작동하게 해서 인가처리를 합니다.
하지만, 둘 다 결국 인가처리를 한다는 점이 공통점이고 권한정보를 얻어야 하기 때문에 두 방식 다 AccessDecisionManager에게 사용자의 권한정보를 전달합니다
→ 각각 FilterInvocationSecurityMetadataSource , MethodSecurityMetadataSource 구현체로부터 권한정보를 가져와서 전달하는 것입니다.

AOP Method 기반 DB 연동

MapBasedSecurityMetadataSource(2)

Method방식도 Map기반으로 DB를 연동해서 처리할 수 있는 방법을 지원합니다.
MapBasedMethodSecurityMetadataSource클래스가 해당 기능을 지원하는데, 이 클래스는 MethodSecurityMetadataSource 인터페이스를 구현하고 있고, 해당 인터페이스도 최상위 인터페이스인 SecurityMetadataSource 인터페이스를 상속받고 있습니다.

특징

어노테이션 설정 방식이 아니라 맵 기반으로 권한을 설정합니다.
기본적인 구현이 완성되어 있고, DB로부터 자원과 권한정보를 매핑한 데이터를 전달하면 메소드 방식의 인가처리가 이뤄지는 클래스입니다.

Flow

사용자가 admin()메소드 호출을 하는데 admin()메서드는 ROLE_ADMIN권한이 설정되어 있습니다.
스프링 초기화 단계에서 admin() 메서드를 가진 객체가 프록시 객체로 생성되었을 것이고, 권한이 설정된 admin()Advice가 등록됩니다.
메서드에 걸린 AdviceMethodSecurityInterceptor 입니다.
MethodSecurityInterceptor는 인가처리중 해당 메서드에 어떤 권한이 설정되있는지에 대한 정보를 MapBasedMethodSecurityMetadataSource 클래스에 권한정보를 요청합니다.
MapBasedMethodSecurityMetadataSource 클래스는 요청을 처리하기위해 내부에 가지고 있는 MethodMap이라는 객체에서 Key(method name): Value(권한정보)를 통해서 권한정보를 추출해냅니다.
MapBasedMethodSecurityMetadataSource 클래스는 추출한 권한정보를 전달하고 권한 목록이 존재할 경우 AccessDecisionManager에게 전달해 인가처리를 진행합니다.

적용 예제

1. security / configs / MethodSecurityConfig

Code

AOP Method 기반 DB 연동

MapBasedSecurityMetadataSource(3)

1. MethodSecurity Structure

위에서 메서드 보안 설정 클래스(MethodSecurityConfig)을 만들고 Map기반으로 메소드 인가 처리를 할 수 있도록 MapBasedMethodSecurityMetadataSource 객체를 만들어서 설정클래스에 전달했습니다.(customMethodSecurityMetadataSource)
MapBasedMethodSecurityMetadataSource 객체는 methodMap이라는 Map객체에 자원/권한정보가 필요합니다.
개발자는 DB로부터 데이터를 읽어서 ResourceMap 이라는 맵 객체를 만들어야 합니다.
MethodResourcesMapFactoryBean
DB로 부터 얻은 권한/자원 정보를 ResourceMap을 빈으로 생성해서 MapBasedMethodSecurityMetadataSource 에 전달합니다.

2. Flow

자동 프록시 생성기(DefaultAdvisorAutoProxyCreator)는 스프링 초기화 시점에 MethodSecurityMetadataSourceAdvisor 에서 빈(Bean)들을 검사하는데, 내부적으로 MethodSecurityMetadataSourcePointcut 에서 프록시 생성 대상 클래스와 어드바이스 등록 대상 메소드를 탐색합니다.
보안이 설정된 메서드를 탐색하며 현재 검사하는 빈(Bean)의 클래스 및 메소드 정보를 MapBaseMethodSecurityMetadataSource에 전달합니다.
MapBaseMethodSecurityMetadataSource는 전달받은 정보를 파싱(parse())해 권한설정(인가처리 어드바이스)을 (MethodSecurityInterceptor)에 등록합니다.

3. 적용 예제

security / configs / MethodSecurityConfig 설정 등록

Code

security / factory / MethodResourcesMapFactoryBean

Code

service / SecurityResourceService 메서드 등록(getMethodResourceList)

Code

AOP Method 기반 DB 연동

ProtectPointcutPostProcessor

기존 메소드 방식의 인가처리 방식에서 패키지명을 사용할 때 문제는 항상 FQCN 으로 지정해야하고, 하나하나 각각 지정해줘야 하는 문제가 있습니다. 그래서 Pointcut방식을 이용해서 권한설정을 ProtectPointcutPostProcessor 을 이용해서 할 수 있습니다.
메소드 방식의 인가처리를 위한 자원 및 권한정보 설정 시 자원에 포인트 컷 표현식을 사용할 수 있도록 지원하는 클래스
빈 후처리기로서 스프링 초기화 과정에서 빈 들을 검사하여 빈이 가진 메소드 중 포인트 컷 표현식과 matching 되는 클래스, 메소드, 권한 정보를 MapBaasedMethodSecurityMetadataSource 에 전달하여 인가처리가 되도록 제공되는 클래스
DB 저장 방식
Method 방식
io.security.service.OrderService.order: ROLE_USER
Pointcut 방식
execution(* io.security.service.*Service.*(..)):ROLE_USER
설정 클래스에서 빈 생성시 접근제한자가 package 범위로 되어 있기 때문에 리플렉션을 이용해 생성합니다.
MethodResourcesMapFactoryBean
DB로부터 얻은 권한 자원 정보를 ResourceMap 빈으로 생성해서 ProtectPointcutPostProcessor에 전달

Flow

전체적인 구조는 다 동일하지만 DB에서 읽어온 포인트컷 권한 정보를 ProtectPointcutPostProcessor 에 전달하면 빈 후처리이기 때문에 자신이 검사하는 빈들 중에서 포인트컷 표현식에 해당하는 TargetClass, Method, ConfigAttributeMapBaseMethodSecurityMetadataSource에 전달합니다.

적용 예제

1. security / factory / MethodResourcesmapFactoryBean 수정

Code

2. security / configs / MethodSecurityConfig 등록

Code

protectPointcutPostProcessor 은 public class가 아니기 때문에 Java의 리플렉션 기능을 이용해야 합니다.

Code
위 코드를 작성 후 간단한 Controller 구현 및 DB에 pointcut을 등록해서 동작할 경우 예외가 발생합니다. 아직 명확히 밝혀지진 않고 설정클래스에서 람다 형식으로 선언 된 빈이 존재할 경우 에러가 발생합니다. try catch를 해줘야하지만, read-only class이기 때문에 수정이 불가능합니다.
그렇기에 우회책으로 내부구조가 동일한 protectPointcutPostProcessor 클래스를 하나 구현해서 예외가 발생하는 부분(attemptMatch)에 try catch 로 surround 해줍니다.

security / processor / ProtectPointcutPostProcessor

Code

security / configs / MethodSecurityConfig 설정 등록

Code