使用 AOP 实现指定 IP 访问控制

1.需求背景

在现代 Web 应用程序中,访问控制是确保系统安全的重要手段。通过限制特定 IP 地址的访问,可以有效阻止未经授权的请求,从而保护敏感数据和服务。本文将介绍如何借助 AOP(面向切面编程),实现基于 IP 白名单的访问控制。

2.什么是 AOP?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于分离程序中的横切关注点(如日志、事务管理、安全验证等),从而提高代码的模块化水平。通过 AOP,可以在不修改核心业务逻辑的情况下动态地添加功能逻辑。

3.实现步骤

通过以下步骤,我们将实现指定 IP 访问控制的功能:

3.1 配置 IP 白名单

首先,在配置文件中定义允许访问的 IP 地址列表:

ip:
  include:
    list:
     - 10.25.17.82
     - 127.0.0.1
     - 10.25.16.219

然后,创建对应的配置类,用于加载和管理这些 IP 地址:

import cn.hutool.core.collection.CollUtil;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

@ConfigurationProperties(prefix = "ip.include")
@Component
@Data
public class DynamicIPAccessProperties {

    private List<String> list;

    /**
     * 检查给定 IP 是否有访问权限
     *
     * @param ip 目标 IP 地址
     * @return 是否允许访问
     */
    public boolean isAccess(String ip) {
        if (StringUtils.isBlank(ip)) {
            return false;
        }
        if (CollUtil.isEmpty(list)) {
            return true; // 若白名单为空,则默认允许访问
        }
        return list.contains(ip);
    }
}

3.2 定义校验注解

定义一个自定义注解,用于标识需要进行 IP 校验的方法:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用于标记需要校验 IP 的方法
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IPAccessControl {

}

3.3 编写 AOP 切面逻辑

使用 AOP 拦截带有 @IPAccessControl 注解的方法,并通过 DynamicIPAccessProperties 校验请求来源 IP:

import com.netvine.patrolrobot.common.exception.util.ServiceExceptionUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;

@Aspect
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicIPAccessControlAspect {

    private final DynamicIPAccessProperties dynamicIPAccessProperties;

    @Pointcut("@annotation(com.netvine.patrolrobot.aop.IPAccessControl)")
    public void ipAccessControl() {
        // 定义切点
    }

    @Before("ipAccessControl()")
    public void before() {
        HttpServletRequest request = getHttpServletRequest();
        if (request == null) {
            log.error("无法获取 HttpServletRequest,请检查请求上下文。");
            throw ServiceExceptionUtil.exception0(500, "HttpServletRequest 获取失败");
        }

        String ip = getIpAddress(request);
        log.info("请求来源 IP: {}", ip);

        if (!dynamicIPAccessProperties.isAccess(ip)) {
            String errorMsg = String.format("当前 IP: [%s] 不在访问范围内", ip);
            log.warn(errorMsg);
            throw ServiceExceptionUtil.exception0(500, errorMsg);
        }
    }

    private HttpServletRequest getHttpServletRequest() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            return (HttpServletRequest) attributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        }
        return null;
    }

    private String getIpAddress(HttpServletRequest request) {
        List<String> headers = Arrays.asList(
                "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP",
                "HTTP_CLIENT_IP", "X-Real-IP"
        );

        for (String header : headers) {
            String ip = request.getHeader(header);
            if (StringUtils.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
                return ip.split(",")[0].trim();
            }
        }

        String ip = request.getRemoteAddr();
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }
}

3.4. 应用到具体控制器方法

最后,将注解应用到需要校验 IP 的控制器方法上:

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/aop/")
@Api(tags = "AOP 测试")
public class AopController {

    @ApiOperation("测试方法")
    @GetMapping("/test")
    @IPAccessControl
    public void test() {
        // 测试方法体
    }
}

总结

通过以上步骤,我们实现了基于 AOP 的 IP 访问控制机制,具有以下优点:

  • 解耦业务逻辑:访问控制逻辑被分离到切面中,核心业务代码更加简洁。
  • 高可维护性:使用注解标记方法,使控制规则更直观。
  • 灵活性:通过配置文件管理 IP 白名单,可动态调整访问规则。 这种设计既保证了系统的安全性,也提升了代码的可读性与维护性,非常适合现代Web 应用的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值