1. 원리 이해
이번에는 Method 방식을 적용하되, 어노테이션을 따로 추가하지 않고 DB에 저장된 resource-role 정보를 받아와서 특정 method에 권한 설정을 해주는 방식을 공부한다.
우선 원리 이해를 위해서 여지껏 공부해온 url방식과 method 방식의 작동 원리를 도식화하여 비교해본다.
url 방식
url 방식에서 요청이 들어오면, FilterSecurityInterceptor를 상속한 객체가 FilterInvocationSecurityMetadataSource 정보를 받았다. 이 정보 안에 RequestMap이라는 Resource-Role 관계 정보가 담겨져 있었고, 이것을 DB로부터 받아서 인가처리를 하는데 사용했다.
실습때 SecurityConfig 설정파일에서 customFilterSecurityInterceptor를 사용했고, 뒷 부분에서 PermitAllFilter로 변경했으나 PermitAllFilter는 FilterSecurityInterceptor를 상속받는 객체였다.
@Bean
public FilterSecurityInterceptor customFilterSecurityInterceptor() throws Exception {
// FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
// //3가지 속성을 설정해주어야 한다.
// filterSecurityInterceptor.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource());
// filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased());
// filterSecurityInterceptor.setAuthenticationManager(authenticationManagerBean());
//
// return filterSecurityInterceptor;
//permitAllFilter 적용
PermitAllFilter permitAllFilter = new PermitAllFilter(permitAllResources);
permitAllFilter.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource());
permitAllFilter.setAccessDecisionManager(affirmativeBased());
permitAllFilter.setAuthenticationManager(authenticationManagerBean());
return permitAllFilter;
}
method 방식
method 방식도 도식처럼 url 방식과 유사하다. 다만 RequestMatcher 대신 Method를 MethodMap에 넣어주었다. 지난 글에서 AbstractSecurityInterceptor의 beforeInvocation 부분에서 attributes(MethodMap) 객체 안에 method-지정한 권한 정보가 담기는 것을 확인했었다.
그리고 Url 방식은 Filter에 의해 동작하는 반면, Method 방식은 MethodMap에 담긴 정보가 각 method에 Advice 형태로 Proxy 객체와 함께 등록되어 Scan 과정을 거쳐 보안처리가 된다는 점이 다른 점이라는 것을 기억하자
이제 어노테이션을 통해서 MethodMap이 만들어지도록 하는 것이 아니라 DB에 있는 정보로 MethodMap을 만들어주기만 하면 된다.
2. 실습
MethodResourcesFactoryBean
urlResourceFactoryBean의 코드를 복사해와서, Map의 key값만 String으로 바꿔준다.
public class MethodResourcesFactoryBean implements FactoryBean<LinkedHashMap<String, List<ConfigAttribute>>> {
private SecurityResourceService securityResourceService;
private LinkedHashMap<String, List<ConfigAttribute>> resourceMap;
public MethodResourcesFactoryBean(SecurityResourceService securityResourceService) {
this.securityResourceService = securityResourceService;
}
private void init() {
resourceMap = securityResourceService.getMethodResourceList();
}
@Override
public boolean isSingleton() {
return true; //1개만 있어서 true로 처리
}
@Override
public LinkedHashMap<String, List<ConfigAttribute>> getObject() {
if(Objects.isNull(resourceMap)) {
init();
}
return resourceMap;
}
@Override
public Class<?> getObjectType() {
return LinkedHashMap.class;
}
}
SecurityResourceService
service의 getMethodResourceList 부분이다. respository에서 findAllByResourceType이라는 spring data jpa 문법을 사용했다.
public LinkedHashMap<String, List<ConfigAttribute>> getMethodResourceList() {
LinkedHashMap<String, List<ConfigAttribute>> result = new LinkedHashMap<>();
List<Resource> resources = resourcesRepository.findAllByResourceType("method");
resources.forEach(resource -> {
List<ConfigAttribute> configAttributes = new ArrayList<>();
Long resourceId = resource.getId();
List<RoleResource> roleResources = roleResourceRepository.findAllByResourceId(resourceId);
roleResources.forEach(roleResource -> {
configAttributes.add(new SecurityConfig(roleResource.getRole().getRoleName())); //ConfigAttribute 타입의 구현체인 SecurityConfig를 넣어준다.
});
result.put(resource.getResourceName(), configAttributes);
});
return result;
}
Resource 등록
repository에서 resource를 찾을 수 있도록 resourceType을 method로 지정해준다. 그리고 resource 이름은 패키지-객체-메서드의 fullname이 되도록 해주어야 한다.
AopMethodService, AopSecurityController
특정 url로 접근 시 특정 메서드가 실행되도록 하기 위해 controller와 service를 작성한다.
AopSecurityController 일부
@GetMapping("/methodSecured")
public String methodSecured(Model model) {
aopMethodService.methodSecured();
model.addAttribute("method", "Success MethodSecured");
return "aop/method";
}
AopMethodService
@Service
public class AopMethodService {
public void methodSecured() {
System.out.println("methodSecured");
}
}
MethodSecurityConfig 등록
마지막으로 설정 파일을 작성한다. Url 방식과 거의 똑같이 작성한다고 보면 된다.
@Configuration
@EnableGlobalMethodSecurity //맵 기반이라 속성값들을 따로 true로 줄 필요가 없음
@RequiredArgsConstructor
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
private final SecurityResourceService securityResourceService;
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return mapBasedMethodSecurityMetadataSource();
}
@Bean
public MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource() {
return new MapBasedMethodSecurityMetadataSource(methodResourcesMapFactoryBean().getObject());
}
@Bean
public MethodResourcesFactoryBean methodResourcesMapFactoryBean() {
return new MethodResourcesFactoryBean(securityResourceService);
}
}
3. 작동 방식 살펴보기
초기화
break point를 걸고 서버를 실행시키면 MethodSecurityConfig -> methodResourcesMapFactoryBean -> init() -> getMethodResourceList()에 의해 DB에 저장된 method 방식 resource에 대한 role 정보를 가져오는 것을 확인할 수 있다.
다음으로 MethodSecurityConfig에서 생성하는 MapBasedMethodSecurityMetadataSource 파일에서 위 methodMap 정보를 받아와서 parsing 한다. "."을 기준으로 파싱해서 class, method 이름을 key로, 그에 해당하는 권한 정보들을 value로 만들어서 저장한다.
다음으로 DelegatingMethodSecurityMetadataSource 클래스에서 위 Map 정보에 따른 실제 클래스 정보들을 attributeCache에 담는다.
마지막으로 MethodSecurityMetadataSourceAdvisor에서 getAdvice() 메서드를 통해 interceptor에 해당 정보를 담아서 AOP 기반으로 동작할 수 있는 기반을 만든다.
자원 접근
이제 자원에 접근해본다. 메소드 보안 메뉴를 클릭한다.
강의 코드가 많이 꼬여있어서 어째저째 해결하면서 왔으나, 뒷 부분은 더 이상 이해가 어렵다. 향후 실무에 적용이 필요하다면 추가적으로 공부를 해야겠다.
이상 스프링 시큐리티 ---
참조
1) 인프런 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 정수원님 강의
https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%E
'Programming-[Backend] > Spring Security' 카테고리의 다른 글
[스프링 시큐리티]24. Method 방식 : 어노테이션 API 이해 (0) | 2022.02.28 |
---|---|
[스프링 시큐리티]23. Method 방식 : 동작방식 및 구조 알아보기 (0) | 2022.02.27 |
[스프링 시큐리티]22. 아이피 접속 제한 ; AccessDecisionVoter 추가 (0) | 2022.02.25 |
[스프링 시큐리티]21. 계층 권한 (0) | 2022.02.25 |
[스프링 시큐리티]20. 실시간 권한 업데이트, 허용필터 (0) | 2022.02.20 |