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 示例项目


原始标题:Spring Security - Whitelist IP Range