Spring Security - 20. AuthorizationFilter.

하쮸·2025년 4월 15일

1. AuthorizationFilter.

  • AuthorizationFilter
    • 사용자가 직접 커스텀 시큐리티필터체인을 등록하더라도 기본적으로 등록됨.
    • AuthorizationManager를 사용하여 URL에 대한 액세스를 제한하는 권한 필터.
httpSecurity
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/").permitAll()
                        .anyRequest().authenticated()
                );

2. 코드.

package org.springframework.security.web.access.intercept;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.function.Supplier;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;

public class AuthorizationFilter extends GenericFilterBean {
    private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
    private final AuthorizationManager<HttpServletRequest> authorizationManager;
    private AuthorizationEventPublisher eventPublisher = new NoopAuthorizationEventPublisher();
    private boolean observeOncePerRequest = false;
    private boolean filterErrorDispatch = true;
    private boolean filterAsyncDispatch = true;

    public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
        Assert.notNull(authorizationManager, "authorizationManager cannot be null");
        this.authorizationManager = authorizationManager;
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        if (this.observeOncePerRequest && this.isApplied(request)) {
            chain.doFilter(request, response);
        } else if (this.skipDispatch(request)) {
            chain.doFilter(request, response);
        } else {
            String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                AuthorizationResult result = this.authorizationManager.authorize(this::getAuthentication, request);
                this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, result);
                if (result != null && !result.isGranted()) {
                    throw new AuthorizationDeniedException("Access Denied", result);
                }

                chain.doFilter(request, response);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }

        }
    }

    private boolean skipDispatch(HttpServletRequest request) {
        if (DispatcherType.ERROR.equals(request.getDispatcherType()) && !this.filterErrorDispatch) {
            return true;
        } else {
            return DispatcherType.ASYNC.equals(request.getDispatcherType()) && !this.filterAsyncDispatch;
        }
    }

    private boolean isApplied(HttpServletRequest request) {
        return request.getAttribute(this.getAlreadyFilteredAttributeName()) != null;
    }

    private String getAlreadyFilteredAttributeName() {
        String name = this.getFilterName();
        if (name == null) {
            name = this.getClass().getName();
        }

        return name + ".APPLIED";
    }

    public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
        Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
        this.securityContextHolderStrategy = securityContextHolderStrategy;
    }

    private Authentication getAuthentication() {
        Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
        if (authentication == null) {
            throw new AuthenticationCredentialsNotFoundException("An Authentication object was not found in the SecurityContext");
        } else {
            return authentication;
        }
    }

    public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
        Assert.notNull(eventPublisher, "eventPublisher cannot be null");
        this.eventPublisher = eventPublisher;
    }

    public AuthorizationManager<HttpServletRequest> getAuthorizationManager() {
        return this.authorizationManager;
    }

    /** @deprecated */
    @Deprecated(
        since = "6.1",
        forRemoval = true
    )
    public void setShouldFilterAllDispatcherTypes(boolean shouldFilterAllDispatcherTypes) {
        this.observeOncePerRequest = !shouldFilterAllDispatcherTypes;
        this.filterErrorDispatch = shouldFilterAllDispatcherTypes;
        this.filterAsyncDispatch = shouldFilterAllDispatcherTypes;
    }

    public boolean isObserveOncePerRequest() {
        return this.observeOncePerRequest;
    }

    public void setObserveOncePerRequest(boolean observeOncePerRequest) {
        this.observeOncePerRequest = observeOncePerRequest;
    }

    public void setFilterErrorDispatch(boolean filterErrorDispatch) {
        this.filterErrorDispatch = filterErrorDispatch;
    }

    public void setFilterAsyncDispatch(boolean filterAsyncDispatch) {
        this.filterAsyncDispatch = filterAsyncDispatch;
    }

    private static class NoopAuthorizationEventPublisher implements AuthorizationEventPublisher {
        private NoopAuthorizationEventPublisher() {
        }

        public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object, AuthorizationDecision decision) {
        }

        public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object, AuthorizationResult result) {
        }
    }
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
        throws ServletException, IOException {

    // ServletRequest와 ServletResponse를 HttpServletRequest/Response로 캐스팅.
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

    // observeOncePerRequest가 true이고, 이미 필터가 적용된 요청이라면 필터를 한 번만 적용하고 바로 다음 필터로 넘김.
    if (this.observeOncePerRequest && this.isApplied(request)) {
        chain.doFilter(request, response);
    } 
    // DispatcherType이 ASYNC(비동기), FORWARD 등이라면 필터 적용을 건너뜀.
    else if (this.skipDispatch(request)) {
        chain.doFilter(request, response);
    } 
    else {
        // 중복 적용을 방지하기 위해 현재 요청에 대해 "이미 필터 적용됨"을 표시.
        String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

        try {
            // 실제 권한 검사 : AuthorizationManager가 Authentication 객체와 Request 정보를 바탕으로 인가 결과를 반환.
            AuthorizationResult result = this.authorizationManager.authorize(this::getAuthentication, request);

            // 인가 이벤트 발생 (추후 감사 로그나 확장 가능)
            this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, result);

            // 인가 실패인 경우 Access Denied 예외를 발생시킴. (인가 결과가 존재하고, 그 결과가 '접근 거부'일 경우)
            if (result != null && !result.isGranted()) {
            // result.isGranted() : true(인가 성공, 즉 요청 허용), false(인가 실패, 즉 요청 차단).
                throw new AuthorizationDeniedException("Access Denied", result);
            }

            // 인가에 성공했으면 다음 필터로 진행.
            chain.doFilter(request, response);
        } finally {
            // 필터 적용 완료 후 "이미 적용" 표시 제거 (다른 필터나 리퀘스트 재사용 방지).
            request.removeAttribute(alreadyFilteredAttributeName);
        }
    }
}
profile
Every cloud has a silver lining.

0개의 댓글