基于SpringBoot實(shí)現(xiàn)IP黑白名單的詳細(xì)步驟
業(yè)務(wù)場景
IP黑白名單是網(wǎng)絡(luò)安全管理中常見的策略工具,用于控制網(wǎng)絡(luò)訪問權(quán)限,根據(jù)業(yè)務(wù)場景的不同,其應(yīng)用范圍廣泛,以下是一些典型業(yè)務(wù)場景:
服務(wù)器安全防護(hù):
- 黑名單:可以用來阻止已知的惡意IP地址或曾經(jīng)嘗試攻擊系統(tǒng)的IP地址,防止這些來源對(duì)服務(wù)器進(jìn)行未經(jīng)授權(quán)的訪問、掃描、攻擊等行為。
- 白名單:僅允許特定IP或IP段訪問關(guān)鍵服務(wù),比如數(shù)據(jù)庫服務(wù)器、內(nèi)部管理系統(tǒng)等,實(shí)現(xiàn)最小授權(quán)原則,降低被未知風(fēng)險(xiǎn)源入侵的可能性。
網(wǎng)站安全防護(hù):
- 黑名單:對(duì)于頻繁發(fā)起惡意請(qǐng)求、爬取數(shù)據(jù)、DDoS攻擊等活動(dòng)的IP,將其加入黑名單以限制其對(duì)網(wǎng)站的訪問。
- 白名單:如果只希望特定合作伙伴、內(nèi)部員工或特定區(qū)域用戶訪問網(wǎng)站內(nèi)容,則可通過白名單來限定合法訪問者的范圍。
API接口保護(hù):
- 對(duì)于對(duì)外提供的API接口,通過設(shè)置IP黑白名單,確保只有經(jīng)過認(rèn)證或信任的系統(tǒng)和客戶端才能調(diào)用接口。
比如比較容易被盜刷的短信接口、文件接口,都需要添加IP黑白名單加以限制。
核心實(shí)現(xiàn)
獲取客戶端IP地址
@UtilityClass
public class IpUtils {
private final String UNKNOWN = "unknown";
private final String X_FORWARDED_FOR = "X-Forwarded-For";
private final String PROXY_CLIENT_IP = "Proxy-Client-IP";
private final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
private final Pattern COMMA_SEPARATED_VALUES_PATTERN = Pattern.compile("\s*,\s*");
/**
* 默認(rèn)情況下內(nèi)網(wǎng)代理的子網(wǎng)可以是(后面有需要可以進(jìn)行配置):
* 1. 10/8
* 2. 192.168/16
* 3. 169.254/16
* 4. 127/8
* 5. 172.16/12
* 6. ::1
*/
private final Pattern INTERNAL_PROXIES = Pattern.compile(
"10\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +
"192\.168\.\d{1,3}\.\d{1,3}|" +
"169\.254\.\d{1,3}\.\d{1,3}|" +
"127\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +
"172\.1[6-9]\.\d{1,3}\.\d{1,3}|" +
"172\.2[0-9]\.\d{1,3}\.\d{1,3}|" +
"172\.3[0-1]\.\d{1,3}\.\d{1,3}|" +
"0:0:0:0:0:0:0:1|::1"
);
/**
* 獲取請(qǐng)求的IP
*
* @return 請(qǐng)求的IP
*/
public String getIp() {
var requestAttributes = RequestContextHolder.getRequestAttributes();
if (Objects.isNull(requestAttributes)) {
return null;
}
var request = ((ServletRequestAttributes) requestAttributes).getRequest();
var ip = getRemoteIp(request);
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader(PROXY_CLIENT_IP);
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader(WL_PROXY_CLIENT_IP);
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 獲取客戶端真實(shí)IP地址,防止使用X-Forwarded-For進(jìn)行IP偽造攻擊,防御思路見類注釋
*
* @return 真實(shí)IP地址
*/
private String getRemoteIp(HttpServletRequest request) {
var remoteIp = request.getRemoteAddr();
var isInternal = INTERNAL_PROXIES.matcher(remoteIp).matches();
if (isInternal) {
var concatRemoteIpHeaderValue = new StringBuilder();
for (var e = request.getHeaders(X_FORWARDED_FOR); e.hasMoreElements(); ) {
if (concatRemoteIpHeaderValue.length() > 0) {
concatRemoteIpHeaderValue.append(", ");
}
concatRemoteIpHeaderValue.append(e.nextElement());
}
var remoteIpHeaderValue = commaDelimitedListToArray(concatRemoteIpHeaderValue.toString());
for (var i = remoteIpHeaderValue.length - 1; i >= 0; i--) {
var currentRemoteIp = remoteIpHeaderValue[i];
if (!INTERNAL_PROXIES.matcher(currentRemoteIp).matches()) {
return currentRemoteIp;
}
}
return null;
} else {
return remoteIp;
}
}
private String[] commaDelimitedListToArray(String commaDelimitedStrings) {
return (commaDelimitedStrings == null || commaDelimitedStrings.isEmpty())
? new String[0]
: COMMA_SEPARATED_VALUES_PATTERN.split(commaDelimitedStrings);
}
}
獲取到客戶端IP后,我們只要比對(duì)客戶端IP是否在配置的白名單/黑名單中即可。 為了簡化使用,可以采用注解的方式進(jìn)行攔截。
新增注解@IpCheck
/**
* IP白名單校驗(yàn)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
public @interface IpCheck {
/**
* 白名單IP列表,支持${...}
*/
@AliasFor("whiteList")
String value() default "";
/**
* 白名單IP列表,支持${...}
*/
@AliasFor("value")
String whiteList() default "";
/**
* 黑名單IP列表,支持${...}
*/
String blackList() default "";
}
新增IpCheckHandlerInterceptorImpl
我們實(shí)現(xiàn)HandlerInterceptor,在接口上進(jìn)行攔截,如果不滿足配置的黑白名單,則拋出異常。
/**
* @author <a href="mailto:gcwm99@gmail.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" >gcdd1993</a>
* Created by gcdd1993 on 2023/9/20
*/
@Component
public class IpCheckHandlerInterceptorImpl implements HandlerInterceptor, EmbeddedValueResolverAware {
private StringValueResolver stringValueResolver;
@Override
public boolean preHandle(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler) {
// 檢查是否有IpWhitelistCheck注解,并且是否開啟IP白名單檢查
if (!(handler instanceof HandlerMethod)) {
return true; // 如果沒有注解或者注解中關(guān)閉了IP白名單檢查,則繼續(xù)處理請(qǐng)求
}
var handlerMethod = (HandlerMethod) handler;
var method = handlerMethod.getMethod();
var annotation = AnnotationUtils.getAnnotation(method, IpCheck.class);
if (annotation == null) {
return true;
}
var clientIp = IpUtils.getIp();
// 檢查客戶端IP是否在白名單中
var whiteList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.whiteList()))
.map(it -> it.split(","))
.orElse(new String[]{}))
.filter(StringUtils::hasText)
.map(String::trim)
.collect(Collectors.toUnmodifiableSet());
if (!whiteList.isEmpty() && whiteList.contains(clientIp)) {
return true; // IP在白名單中,繼續(xù)處理請(qǐng)求
}
var blackList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.blackList()))
.map(it -> it.split(","))
.orElse(new String[]{}))
.filter(StringUtils::hasText)
.map(String::trim)
.collect(Collectors.toUnmodifiableSet());
if (!blackList.isEmpty() && !blackList.contains(clientIp)) {
return true; // IP不在黑名單中,繼續(xù)處理請(qǐng)求
}
// IP不在白名單中,可以返回錯(cuò)誤響應(yīng)或者拋出異常
// 例如,返回一個(gè) HTTP 403 錯(cuò)誤
throw new RuntimeException("Access denied, remote ip " + clientIp + " is not allowed.");
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.stringValueResolver = resolver;
}
}
自動(dòng)裝配
核心邏輯寫完了,該怎么使用呢?為了達(dá)到開箱即用的效果,我們可以接著新增自動(dòng)裝配的代碼
新建IpCheckConfig
實(shí)現(xiàn)WebMvcConfigurer接口,添加接口攔截器
/**
* @author <a href="mailto:gcwm99@gmail.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" >gcdd1993</a>
* Created by gcdd1993 on 2024/1/24
*/
public class IpCheckConfig implements WebMvcConfigurer {
@Resource
private IpCheckHandlerInterceptorImpl ipCheckHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipCheckHandlerInterceptor);
}
}
新建@EnableIpCheck
參考@EnableScheduling的實(shí)現(xiàn),自己實(shí)現(xiàn)一個(gè)@EnableIpCheck,該注解可以控制功能是否啟用
/**
* @author <a href="mailto:gcwm99@gmail.com" rel="external nofollow" rel="external nofollow" rel="external nofollow" >gcdd1993</a>
* Created by gcdd1993 on 2024/1/24
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@Documented
@ComponentScan("xxx.ip") // 這里是IpCheckConfig的包名
@Import(IpCheckConfig.class)
public @interface EnableIpCheck {
}
業(yè)務(wù)測試
簡單地用代碼來試驗(yàn)下效果
新建SampleApplication
@SpringBootApplication
@EnableIpCheck
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
新建測試接口
@RestController
@RequestMapping("/sample/ip-checker")
public class IpCheckSample {
@GetMapping("/white")
@IpCheck(value = "0:0:0:0:0:0:0:1")
String whiteList() {
return "127.0.0.1";
}
@GetMapping("/black")
@IpCheck(blackList = "0:0:0:0:0:0:0:1")
String blackList() {
return "127.0.0.1";
}
/**
* 同時(shí)配置白名單和黑名單,要求IP既在白名單,并且不在黑名單,否則拋出異常
*/
@GetMapping("/all")
@IpCheck(value = "0:0:0:0:0:0:0:1", blackList = "0:0:0:0:0:0:0:1")
String all() {
return "127.0.0.1";
}
/**
* 同時(shí)配置白名單和黑名單,要求IP既在白名單,并且不在黑名單,否則拋出異常
* 支持解析Spring 配置文件
*/
@GetMapping("/config")
@IpCheck(value = "${digit.ip.check.white-list}", blackList = "${digit.ip.check.black-list}")
String config() {
return "127.0.0.1";
}
/**
* 同時(shí)配置白名單和黑名單,要求IP既在白名單,并且不在黑名單,否則拋出異常
* 支持解析Spring 配置文件
*/
@GetMapping("/black-config")
@IpCheck(blackList = "${digit.ip.check.black-list}")
String blackConfig() {
return "127.0.0.1";
}
}
由于本機(jī)請(qǐng)求IP地址是0:0:0:0:0:0:0:1,所以這里使用0:0:0:0:0:0:0:1而不是127.0.0.1。
訪問/sample/ip-checker/white
接口返回127.0.0.1
訪問/sample/ip-checker/black
java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.
訪問/sample/ip-checker/all
接口返回127.0.0.1
- 既配置白名單,也配置黑名單,需要既不在白名單,同時(shí)在黑名單里,才會(huì)攔截。
修改配置
digit:
ip:
check:
white-list: 127.0.0.1, 192.168.1.1, 192.168.1.2
black-list: 127.0.0.1, 192.168.1.1, 192.168.1.2,0:0:0:0:0:0:0:1
訪問/sample/ip-checker/black-config
java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.
最后,可以結(jié)合配置中心,以便配置后立即生效。
以上就是基于SpringBoot實(shí)現(xiàn)IP黑白名單的詳細(xì)步驟的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot IP黑白名單的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis框架之模板方法模式(Template Method Pattern)的實(shí)現(xiàn)
MyBatis中使用了模板方法模式來控制SQL語句的執(zhí)行流程,本文主要介紹了Mybatis框架之模板方法模式(Template Method Pattern)的實(shí)現(xiàn),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
spring+Jpa多數(shù)據(jù)源配置的方法示例
這篇文章主要介紹了spring+Jpa多數(shù)據(jù)源配置的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
基于OAuth2.0授權(quán)系統(tǒng)的驗(yàn)證碼功能的實(shí)現(xiàn)
本篇教程給大家分享基于OAuth2.0授權(quán)系統(tǒng)的驗(yàn)證碼功能的實(shí)現(xiàn),驗(yàn)證碼功能的實(shí)現(xiàn)是采用Zuul網(wǎng)關(guān)的Filter過濾器進(jìn)行校驗(yàn)驗(yàn)證碼,具體實(shí)現(xiàn)代碼跟隨小編一起看看吧2021-05-05
解決springboot啟動(dòng)時(shí)報(bào)錯(cuò)的問題ApplicationEventMulticaster not&nbs
這篇文章主要介紹了解決springboot啟動(dòng)時(shí)報(bào)錯(cuò)的問題ApplicationEventMulticaster not initialized,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-06-06
基于SpringBoot實(shí)現(xiàn)代碼在線運(yùn)行工具
這篇文章主要介紹了如何利用SpringBoot實(shí)現(xiàn)簡單的代碼在線運(yùn)行工具(類似于菜鳥工具),文中的示例代碼講解詳細(xì),需要的可以參考一下2022-06-06

