Programming/Spring Boot

스프링부트 spring-security-web을 사용한 IP 접근제어

Jan92 2022. 11. 2. 00:11
반응형

spring-sucurity-web

해당 포스팅은 spring-security-web의 IpAddressMatcher와 spring의 interceptor를 활용하여 지정된 ip만 해당 서비스에 접근 가능하도록 설정하는 ip 접근제어를 구현한 내용입니다.

(spring-security-web은 org.springframework.boot:spring-boot-starter-security:2.7.3에 포함되어 있습니다.)

 

 

Interceptor

@Component
@RequiredArgsConstructor
public class IpAddressAccessControlInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // ip 접근제어 로직 구현 예정
    }
}

(IpAddressAccessControlInterceptor class)

 

HandlerInterceptor interface를 구현한 Interceptor를 구현합니다.

해당 Interceptor에서는 컨트롤러가 호출되기 전에 실행되는 preHandle() method를 통해 ip 접근제어 로직을 구현할 예정이며, 해당 인터셉터를 Bean으로 등록하여 사용할 것이기 때문에 @Component 어노테이션을 붙여줍니다.

 

 

WebMvcConfiguration

@Configuration
@RequiredArgsConstructor
public class WebMvcConfiguration implements WebMvcConfigurer {

    private final IpAddressAccessControlInterceptor ipAddressAccessControlInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(ipAddressAccessControlInterceptor)
                .order(1)
                .addPathPatterns("/url1", "/url2")
                .excludePathPatterns("/resources/**", "/error/**");
    }
}

(WebMvcConfiguration class)

 

위에서 구현한 Interceptor는 WebMvcConfigurer 인터페이스의 구현체에서 addInterceptors() 메서드를 override 하여 등록하게 되는데요.

addInterceptor() 메서드는 interceptor를 등록하는 메서드이며, order()의 경우 인터셉터가 여러 개 있을 때 순서를 정의하기 위한 메서드입니다. addPathPatterns() 메서드를 통해 해당 인터셉터를 적용시킬 경로를 설정할 수 있으며, excludePathPatterns() 메서드를 통해 인터셉터를 적용하지 않고자 하는 경로를 설정할 수 있습니다.

 

 

getClientIp

public static String getClientIp(HttpServletRequest request) {
    String clientIp = null;
    boolean isIpInHeader = false;

    List<String> headerList = new ArrayList<>();
    headerList.add("X-Forwarded-For");
    headerList.add("HTTP_CLIENT_IP");
    headerList.add("HTTP_X_FORWARDED_FOR");
    headerList.add("HTTP_X_FORWARDED");
    headerList.add("HTTP_FORWARDED_FOR");
    headerList.add("HTTP_FORWARDED");
    headerList.add("Proxy-Client-IP");
    headerList.add("WL-Proxy-Client-IP");
    headerList.add("HTTP_VIA");
    headerList.add("IPV6_ADR");

    for (String header : headerList) {
        clientIp = request.getHeader(header);
        if (StringUtils.hasText(clientIp) && !clientIp.equals("unknown")) {
            isIpInHeader = true;
            break;
        }
    }

    if (!isIpInHeader) {
        clientIp = request.getRemoteAddr();
    }
    return clientIp;
}

(Helper class에 구현된 클라이언트의 ip 주소를 가져오는 getClientIp 메서드)

 

ip 접근제어를 위해서 요청하는 클라이언트의 ip를 가져오는 메서드입니다. getClientIp() 메서드에 관한 자세한 내용은 바로 아래 참고 자료를 통해 좀 더 자세하게 살펴보실 수 있습니다.

2022.06.07 - [Programming/Java] - Java 클라이언트 요청 IP 가져오는 방법(HttpServletRequest)

 

 

IpAddressMatcherManager

@Component
public class IpAddressMatcherManager {

    @Getter
    private List<IpAddressMatcher> ipAddressMatchers;

    public IpAddressMatcherManager() {
        this.ipAddressMatchers = Arrays.asList(
                new IpAddressMatcher("192.168.1.0/24"),
                new IpAddressMatcher("192.168.2.0/24")
        );
    }

    public boolean isAccessible(String ipAddress) {
        //List<IpAddressMatcher> ipAddressMatchers = this.getIpAddressMatchers();
        return ipAddressMatchers.stream().anyMatch(matcher -> matcher.matches(ipAddress));
    }
}

(IpAddressMatcherManager class)

 

IpAddressMatcher는 Spring Security에서 제공하는 IP 매칭 여부를 판단하기 위한 객체인데요. 원격 주소를 ip 주소나 서브넷 마스크를 기반으로 매칭 하며, IPv4 및 IPv6 모두를 지원합니다.

(IPv4와 IPv6 서로 간의 매칭은 지원되지 않습니다.)

 

접근 가능한 ip로 생성된 IpAddressMatcher의 리스트인 ipAddressMatchers 같은 경우는 상황에 따라 위 예시와 같은 하드코딩이 아니라 properties에 저장된 list를 가져오거나 DB에 저장된 list를 가져오는 방식을 적용할 수 있습니다.

 

매칭 여부를 확인하는 방법은 IpAddressMatcher 객체의 matches() 메서드를 통해 확인할 수 있습니다.

 

 

Interceptor

@Slf4j
@RequiredArgsConstructor
@Component
public class IpAddressAccessControlInterceptor implements HandlerInterceptor {

    private final IpAddressMatcherManager ipAddressMatcherManager;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //getClientIp
        String clientIpAddress = Helper.getClientIp(request);

        if (!ipAddressMatcherManager.isAccessible(clientIpAddress)) {
            String requestURI = request.getRequestURI();
            log.warn("Forbidden access. request uri={}, client ip={}", requestURI, clientIpAddress);

            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            return false;
        }
        return true;
    }
}

(IpAddressControlInterceptor class)

 

최종적으로 구현된 Interceptor입니다. isAccessible() 메서드의 결과에 대한 응답은 프로세스 로직에 따라 수정해서 사용하면 될 것 같습니다.

 

 

TEST

Postman을 통한 test

WARN 1840 --- [nio-8082-exec-7] .t.l.i.IpAddressAccessControlInterceptor : Forbidden access. request uri=/test, client ip=192.168.3.0

허용되지 않은 ip의 경우 해당 인터셉터를 통해 접근이 불가능한 것을 확인할 수 있습니다.

 

 

 

< 참고 자료 >

https://atoz-develop.tistory.com/entry/Spring-Security-Web%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-IP-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

반응형