1. 概述
在这篇文章中,我们来聊聊如何在 Spring Security 中实现 IP 白名单机制。
我们会分别展示基于 Java 配置和 XML 配置的方式,还会介绍如何通过自定义 AuthenticationProvider
来实现更灵活的 IP 控制策略。
如果你的系统只希望某些特定 IP 地址可以访问敏感资源,这个功能就非常实用。比如限制某个后台接口只能从公司内网访问。
2. Java 配置方式
先来看 Java 配置怎么玩。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry.requestMatchers("/login").permitAll()
.requestMatchers("/foos/**")
.access(new WebExpressionAuthorizationManager("isAuthenticated() and hasIpAddress('11.11.11.11')")).anyRequest().authenticated())
.formLogin(AbstractAuthenticationFilterConfigurer::permitAll)
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
✅ 这段配置的意思是:
/login
接口所有人都可以访问;/foos/**
接口只有来自 IP 地址为11.11.11.11
的用户,并且已经登录了才能访问;- 其他所有请求都需要认证。
⚠️ 注意:如果想让白名单 IP 也必须登录后才能访问,需要加上 isAuthenticated()
,如上所示。
3. XML 配置方式
当然,如果你还在用 XML 配置(虽然现在比较少见),也可以这么写:
<security:http>
<security:form-login/>
<security:intercept-url pattern="/login" access="permitAll()" />
<security:intercept-url pattern="/foos/**" access="hasIpAddress('11.11.11.11')" />
<security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>
这和 Java 配置的效果是一样的,只是写法不同。hasIpAddress()
是 Spring Security 提供的一个表达式方法,用于判断客户端 IP 是否在允许范围内。
4. 实际测试一下
光说不练假把式,我们来写个简单的集成测试验证下效果。
✅ 正常用户访问首页
@Test
public void givenUser_whenGetHomePage_thenOK() {
Response response = RestAssured.given().auth().form("john", "123")
.get("http://localhost:8082/");
assertEquals(200, response.getStatusCode());
assertTrue(response.asString().contains("Welcome"));
}
❌ 非白名单 IP 访问受保护资源
@Test
public void givenUserWithWrongIP_whenGetFooById_thenForbidden() {
Response response = RestAssured.given().auth().form("john", "123")
.get("http://localhost:8082/foos/1");
assertEquals(403, response.getStatusCode());
assertTrue(response.asString().contains("Forbidden"));
}
📌 因为我们只允许 11.11.11.11
访问 /foos/**
路径,所以本地 127.0.0.1
就会被拒绝访问。
5. 使用自定义 AuthenticationProvider 实现白名单控制
前面的方式比较粗暴,如果我们要支持多个 IP 白名单,或者需要结合数据库动态判断,那就要用到自定义的 AuthenticationProvider
。
来看个例子:
@Component
public class CustomIpAuthenticationProvider implements AuthenticationProvider {
Set<String> whitelist = new HashSet<String>();
public CustomIpAuthenticationProvider() {
whitelist.add("11.11.11.11");
whitelist.add("12.12.12.12");
}
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails();
String userIp = details.getRemoteAddress();
if(! whitelist.contains(userIp)){
throw new BadCredentialsException("Invalid IP Address");
}
//...
}
这段代码的核心在于:
- 在构造函数中预设了两个白名单 IP;
- 通过
WebAuthenticationDetails.getRemoteAddress()
获取用户的真实 IP; - 如果不在白名单中,抛出异常,拒绝登录。
接着,在安全配置中启用这个 Provider:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomIpAuthenticationProvider authenticationProvider;
@Bean
public InMemoryUserDetailsManager userDetailsService(HttpSecurity http) throws Exception {
UserDetails user = User.withUsername("john")
.password("{noop}123")
.authorities("ROLE_USER")
.build();
http.getSharedObject(AuthenticationManagerBuilder.class)
.authenticationProvider(authenticationProvider)
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry.requestMatchers("/login").permitAll()
.requestMatchers("/foos/**")
.access(new WebExpressionAuthorizationManager("isAuthenticated() and hasIpAddress('11.11.11.11')")).anyRequest().authenticated())
.formLogin(AbstractAuthenticationFilterConfigurer::permitAll)
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
💡 这样一来,登录时就会先校验 IP 是否在白名单中,然后再走正常的用户名密码验证流程。
你可以进一步扩展这个逻辑,比如将白名单存储在数据库中,或者与用户绑定,实现更复杂的权限控制。
6. 总结
这篇文章我们讲了三种在 Spring Security 中设置 IP 白名单的方法:
方式 | 描述 |
---|---|
✅ Java 配置 | 使用 hasIpAddress() 表达式进行简单匹配 |
✅ XML 配置 | 同样的表达式,适用于传统项目 |
✅ 自定义 AuthenticationProvider | 更加灵活可控,适合复杂业务场景 |
这些技巧在保护后台管理页面、API 接口时特别有用。
源码地址:GitHub 示例项目