HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
FilterSecurityInterceptor
  • 필터 체인 상에서 가장 마지막에 위치하며, 사용자가 갖고 있는 권한과 리소스에서 요구하는 권한을 취합하여 접근을 허용할지 결정함
    • 실질적으로 접근 허용 여부 판단은 AccessDecisionManager 인터페이스 구현체에서 이루어짐
  • 작동방식
    • FilterSecurityInterceptor의 doFilter()에서 invoke() 호출
    • invoke() 에서 AbstractSecurityInterceptor의 beforeInvocation() 호출
    • attemptAuthorization() 에서 AccessDecisionManager의 decide() 호출
    • 해당 메서드에서 이 요청이 통과해도 되는 요청인지 투표함(AccessDecisionVoter)
// FilterSecurityInterceptor public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException { if (isApplied(filterInvocation) && this.observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); return; } // first time this request being called, so perform security checking if (filterInvocation.getRequest() != null && this.observeOncePerRequest) { filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(filterInvocation); try { filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); } //beforeInvocation => AbstractSecurityInterceptor protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass()); } else { Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); if (CollectionUtils.isEmpty(attributes)) { Assert.isTrue(!this.rejectPublicInvocations, () -> { return "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'"; }); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authorized public object %s", object)); } this.publishEvent(new PublicInvocationEvent(object)); return null; } else { if (SecurityContextHolder.getContext().getAuthentication() == null) { this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } Authentication authenticated = this.authenticateIfRequired(); if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes)); } this.attemptAuthorization(object, attributes, authenticated); /* ... */ } private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) { try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException var5) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager)); } else if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes)); } this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var5)); throw var5; } }
FilterSecurityInterceptor.invoke( ) → AbstractSecurityInterceptor.beforeInvocation( ) → AbstractSecurityInterceptor.attemptAuthorization( )
  • 해당 필터가 호출되는 시점에서 사용자는 이미 인증이 완료되고, Authentication 인터페이스의 getAuthorities() 메소드를 통해 인증된 사용자의 권한 목록을 가져올수 있음
    • 익명 사용자도 인증이 완료된 것으로 간주하며, ROLE_ANONYMOUS 권한을 갖음
  • 보호되는 리소스에서 요구하는 권한 정보는 SecurityMetadataSource 인터페이스를 통해 ConfigAttribute 타입으로 가져옴
    • Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
[Introduction to Spring Method Security]
  • FilterSecurityInterceptor : antMatcher로 정의되는 url에 대한 permission을 보고 처리함
  • MethodSecurityInterceptor : @PreAuthorize 혹은 @PostAuthorize 와 같은 어노테이션이 붙어있는 메서드에 대해 처리를 함
  • SecurityMetadataSource : 권한 판정을 하기 위해서는 Config attribute가 필요한데, 그것을 모아놓은 map임
    • FilterSecurityInterceptor와 MethodSecurityInterceptor가 각각 다른 SecurityMetadataSource를 가지게 됨
  • MethodSecurityInterceptor를 가능하게 해주는 어노테이션@EnableGlobalMethodSecurity(prePostEnabled = true)
    • 해당 어노테이션은 @Configuration 클래스에다가 붙여서 사용해야 함
  • 권한 체크는 FilterSecurityInterceptor에서 한번, 그리고 @PreAuthorize 어노테이션이 붙어있는 곳에서 반복적으로 다 체크를 하게 됨
notion image
Filter 권한 위원회 = FilterSecurityInterceptor. Global Method 권한 위원회 = MethodSecurityInterceptor
Filter 권한 위원회 = FilterSecurityInterceptor. Global Method 권한 위원회 = MethodSecurityInterceptor