AuthorizationFilter.java

package sprout.security.filter;

import sprout.beans.InfrastructureBean;
import sprout.core.filter.Filter;
import sprout.core.filter.FilterChain;
import sprout.mvc.http.HttpRequest;
import sprout.mvc.http.HttpResponse;
import sprout.mvc.http.ResponseCode;
import sprout.mvc.http.ResponseEntity;
import sprout.security.authorization.AuthorizationRule;
import sprout.security.authorization.exception.AccessDeniedException;
import sprout.security.context.SecurityContextHolder;
import sprout.security.core.Authentication;
import sprout.security.core.GrantedAuthority;
import sprout.security.web.util.matcher.RequestMatcher;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class AuthorizationFilter implements Filter, InfrastructureBean {

    private final List<AuthorizationRule> authorizationRules;

    public AuthorizationFilter(List<AuthorizationRule> authorizationRules) {
        this.authorizationRules = authorizationRules;
    }

    @Override
    public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws IOException {
        Optional<AuthorizationRule> matchedRule = authorizationRules.stream()
                .filter(rule -> rule.getRequestMatcher().matches(request))
                .findFirst(); // 가장 먼저 매칭되는 규칙 사용

        if (matchedRule.isPresent()) {
            AuthorizationRule rule = matchedRule.get();

            if (rule.isPermitAll()) {
                chain.doFilter(request, response);
                return;
            }

            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

            if (authentication == null || !authentication.isAuthenticated()) {
                ResponseEntity<?> responseEntity = new ResponseEntity<>("Authentication required.", null, ResponseCode.UNAUTHORIZED);
                response.setResponseEntity(responseEntity);
                return;
            }

            if (rule.isAuthenticated()) {
                chain.doFilter(request, response);
                return;
            }

            if (!rule.getRequiredAuthorities().isEmpty()) {
                Collection<? extends GrantedAuthority> userAuthorities = Optional.ofNullable(authentication.getAuthorities())
                        .orElse(Collections.emptyList());

                Set<String> userAuthorityStrings = userAuthorities.stream()
                        .map(GrantedAuthority::getAuthority)
                        .collect(Collectors.toSet());

                boolean hasAnyRequiredAuthority = rule.getRequiredAuthorities().stream()
                        .anyMatch(userAuthorityStrings::contains);

                if (hasAnyRequiredAuthority) {
                    chain.doFilter(request, response);
                    return;
                } else {
                    throw new AccessDeniedException("Access Denied: User '" + authentication.getPrincipal() +
                            "' does not have any of the required authorities: " + rule.getRequiredAuthorities());
                }
            }
            // 규칙이 있지만 어떤 조건에도 해당하지 않는 경우 (방어적)
            throw new AccessDeniedException("Access Denied: No matching authorization criteria for authenticated user.");

        } else {
            // 여기서는 매칭되는 규칙이 없으면 필터 체인 통과 (다음 필터 또는 컨트롤러로)
            chain.doFilter(request, response);
        }
    }
}