@EnableGlobalMethodSecurity(securedEnabled = true) 를 설정해 줘야한다.
초기화 과정
DefaultAdvisorAutoProxyCreator 는 빈을 검사하면서 보안(@Secured)이 걸려있는 메소드를 찾아서, 그 메소드를 가지고 있는 빈의 프록시 객체를 생성한다.
프록시 객체를 생성하는 과정은 아래와 같다.
먼저 DefaultAdvisorAutoProxyCreator 는 MethodSecurityMetadataSourceAdvisor 를 통해서 빈을 검사한다.
MethodSecurityMetadataSourceAdvisor 는 MethodSecurityMetadataSourcePointcut 와 MethodSecurityInterceptor 를 가지고 있다.
MethodSecurityMetadataSourcePointcut 는 MethodSecurityMetadataSource 를 사용해서 메소드에 보안이 설정되어 있는지 탐색한다.
정보를 전달받은 MethodSecurityMetadataSource 는 class 정보와 method 정보를 파싱해서 보안 메소드가 설정된 빈일 경우 프록시 생성 대상으로 간주하고 DefaultAdvisorAutoProxyCreator 는 프록시 객체를 생성한다.
또한 MethodMap 에 Key 는 method 명, value 는 권한으로 저장된다.
MethodeSecurityInterceptor 가 Advice 이다.
그렇기 때문에 보안 메소드일 경우에 MethodeSecurityInterceptor 에 어드바이스가 등록된다.
MethodeSecurityInterceptor 가 보안이 걸려있는 order() 메소드 전후로 부가기능(인가처리)을 처리하는 역할을 한다.
실행과정
먼저 사용자가 order(), display() 메소드를 호출했다고 해보자.
order 의 경우 @Secured 가 걸려있고 display 의 경우 걸려있지 않다.
그렇기 때문에 display 는 advice 가 등록되지 않을 것이고, 보안이 적용되지 않을 것이다.
order 의 경우 MethodSecurityInterceptor 에 advice 가 등록되어 있을 것이고, MethodSecurityInterceptor 는 인가를 처리한다.
만약 인가 처리가 승인된다면 프록시가 아닌 실제 OrderService 의 order 메소드가 실행된다.
만약 인가 처리가 승인되지 않았다면 AccessDeniedException 이 발생한다.
Filter 기반 Url 방식, AOP 기반 Method 방식 차이
두 방식은 사용되는 기술과 클래스가 다를 뿐이다.
처리되는 방식도 비슷하다.
먼저 url 의 경우 FilterSecurityInterceptor 라는 필터가 인가를 처리한다.
method 의 경우 MethodSecurityInterceptor 라는 Advice 가 인가를 처리한다.
FilterSecurityInterceptor, MethodSecurityInterceptor 모두 AccessDecisionManager 에게 권한정보를 넘겨주는 것이 목적이다.
서버가 구동될때 초기화되는데, url 방식의 경우 RequestMap 에 권한 정보가 저장되고, method 방식의 경우, MethodMap 에 권한 정보가 저장된다.
FilterSecurityInterceptor 는 FilterInvocationSecurityMetadataSource 에게 권한 정보를 요청하고, MethodSecurityInterceptor 의 경우엔 MethodSecurityMetadataSource 에게 권한 정보를 요청해서 반환 받는다.
각각 반환 받은 권한 정보를 AccessDecisionManager 에게 전달한다.
Map 기반 DB 연동
애노테이션 설정 방식이 아닌 맵 기반으로 권한을 설정하는 방식이다. -> 하지만 애노테이션 방식과 마찬가지로 AOP 기반으로 처리된다.
기본적인 구현이 완성되어 있다.
DB 로부터 자원과 권한정보를 매핑한 데이터를 전달하면 메소드 방식의 인가처리가 이루어 진다.
먼저 사용자가 admin 요청을 보낸다. admin 메소드는 ROLE_ADMIN 권한이 필요하다.
MethodSecurityInterceptor 는 MapBasedMethodSecurityMetadataSource 에게 권한 정보를 요청한다.
MapBasedMethodSecurityMetadataSource 는 MethodMap 를 가지고 있고, MethodMap 에는 위와 같이 권한정보들이 저장되어 있다.
MapBasedMethodSecurityMetadataSource 에 methodMap 을 넣어줘야 한다.
DB 에서 권한 정보를 가져와 [패키지 + 클래스 + 메소드 | 권한] 으로 매핑해서 methodMap 을 구성한다.
애노테이션 방식과 처리과정이 같다.
사용자가 order() 를 요청한다면 order() 를 가지고있는 빈의 프록시 객체가 MethodSecurityInterceptor 에 등록된 advice 로 인가처리를 진행한다.
코드 구현
MethodResourcesMapFactoryBean 구현
@Slf4j
public class MethodResourcesMapFactoryBean implements FactoryBean<LinkedHashMap<String, List<ConfigAttribute>>> {
private SecurityResourceService securityResourceService;
private String resourceType;
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public void setSecurityResourceService(SecurityResourceService securityResourceService) {
this.securityResourceService = securityResourceService;
}
private LinkedHashMap<String, List<ConfigAttribute>> resourcesMap;
public void init() {
if ("method".equals(resourceType)) {
resourcesMap = securityResourceService.getMethodResourceList();
}else if("pointcut".equals(resourceType)){
resourcesMap = securityResourceService.getPointcutResourceList();
}
}
public LinkedHashMap<String, List<ConfigAttribute>> getObject() {
if (resourcesMap == null) {
init();
}
return resourcesMap;
}
@SuppressWarnings("rawtypes")
public Class<LinkedHashMap> getObjectType() {
return LinkedHashMap.class;
}
public boolean isSingleton() {
return true;
}
}
MethodResourcesMapFactoryBean 은 DB로 부터 얻은 권한/자원 정보를 ResourceMap 을 빈으로 생성해서 MapBasedMethodSecurityMetadataSource 에 전달한다.
public LinkedHashMap<String, List<ConfigAttribute>> getMethodResourceList() {
LinkedHashMap<String, List<ConfigAttribute>> result = new LinkedHashMap<>();
List<Resources> resourcesList = resourcesRepository.findAllMethodResources();
resourcesList.forEach(re ->
{
List<ConfigAttribute> configAttributeList = new ArrayList<>();
re.getRoleSet().forEach(ro -> {
configAttributeList.add(new SecurityConfig(ro.getRoleName()));
});
result.put(re.getResourceName(), configAttributeList);
}
);
return result;
}
MapBaseMethodSecurityMetadataSource 에게 넘겨줄 resourceMap 을 만드는 과정이다.
메소드 계층에서의 보안을 위한 MethodSecurityConfig 파일 생성한다.
@Configuration
@EnableGlobalMethodSecurity
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private SecurityResourceService securityResourceService;
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return mapBasedMethodSecurityMetadataSource();
}
@Bean
public MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource() {
return new MapBasedMethodSecurityMetadataSource(methodResourcesMapFactoryBean().getObject());
}
@Bean
public MethodResourcesMapFactoryBean methodResourcesMapFactoryBean(){
MethodResourcesMapFactoryBean methodResourcesMapFactoryBean = new MethodResourcesMapFactoryBean();
methodResourcesMapFactoryBean.setSecurityResourceService(securityResourceService);
methodResourcesMapFactoryBean.setResourceType("method");
return methodResourcesMapFactoryBean;
}
}
위에서 구현한 MethodResourcesMapFactoryBean 을 빈으로 등록한다. -> 그러기 위해서는 securityResourceService 를 MethodResourcesMapFactoryBean 에 등록해 줘야한다.
methodResourcesMapFactoryBean().getObject() 를 통해서 resoureMap 을 MapBasedMethodSecurityMetadataSource 에게 전달한다.
MapBasedMethodSecurityMetadataSource 을 빈으로 등록하고 재정의한 customMethodSecurityMetadataSource 메소드의 반환값으로 넘겨준다.
ProtectPointcutPostProcessor
메소드 방식의 인가처리를 위한 자원 및 권한정보 설정 시 자원에 포인트 컷 표현식을 사용할 수 있도록 지원하는 클래스다.
DB 로 부터 Pointcut 표현식과 권한 정보를 가져와서 ResourceMap 을 생성한다.
ResourceMap 을 ProtextPointcutPostProcessor 에 담겨있는 PointcutMap 에 전달한다.
ProtextPointcutPostProcessor 는 PointcutMap 에 담겨있는 정보를 바탕으로 찾은 빈의 TargetClass, Method, ConfigAttribute 정보를 추출해서 MapBaseMethodSecurityMetadataSource 에게 전달한다.
그 외에는 Map 기반의 DB 연동 방식과 같다.
MethodResourcesMapFactoryBean 은 DB로 부터 얻은 권한/자원 정보를 ResourceMap 빈으로 생성해서 ProtectPointcutPostProcessor 에 전달한다.