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 应用的需求。