如何在Spring Security中为/oauth/check_token端点配置IP白名单
嘿,针对你这种分离部署认证服务器和资源服务器、采用client_credentials授权类型的场景,要保护/oauth/check_token端点只允许指定资源服务器IP访问,我给你两个实用的实现方案,都是基于Spring Security的特性来做的:
在你的AuthorizationServerConfig类里,重写configure(AuthorizationServerSecurityConfigurer security)方法,结合Spring Security的hasIpAddress()表达式,给/oauth/check_token端点加上IP白名单限制:
@EnableAuthorizationServer @Configuration public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // 替换成你的资源服务器IP,多个IP用逗号分隔,比如"192.168.1.100,192.168.1.101" private static final String ALLOWED_RESOURCE_SERVER_IPS = "192.168.1.100"; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security // 仅允许已认证的客户端 + 白名单IP访问check_token端点 .checkTokenAccess("isAuthenticated() and hasIpAddress('" + ALLOWED_RESOURCE_SERVER_IPS + "')") // 保留你原来的tokenKey访问规则 .tokenKeyAccess("permitAll()"); // 其他原有配置(比如tokenStore、clientDetailsService等)继续保留 } // 你的其他配置方法... }
这个方案的好处是简单直接,完全利用Spring Security的表达式语言实现,不需要额外写过滤器。如果资源服务器是一个IP段,还可以用CIDR格式,比如192.168.1.0/24来匹配整个网段。
方案2:自定义IP拦截过滤器(更灵活的场景)
如果你的白名单IP需要从配置文件读取、或者要处理代理场景下的真实客户端IP,那自定义过滤器会更合适:
首先创建一个IP校验过滤器:
@Component public class IpWhitelistFilter extends OncePerRequestFilter { // 从配置文件读取允许的IP列表,比如在application.properties里配置:resource.server.ips=192.168.1.100,192.168.1.101 @Value("#{'${resource.server.ips}'.split(',')}") private List<String> allowedIps; private static final String CHECK_TOKEN_ENDPOINT = "/oauth/check_token"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 只对check_token端点做IP校验 if (CHECK_TOKEN_ENDPOINT.equals(request.getRequestURI())) { String clientIp = getRealClientIp(request); if (!allowedIps.contains(clientIp.trim())) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write("Access Denied: Your IP is not whitelisted"); return; } } // 符合条件就继续执行后续过滤器 filterChain.doFilter(request, response); } // 处理代理场景,获取真实客户端IP private String getRealClientIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } // 如果X-Forwarded-For有多个IP,取第一个(最原始的客户端IP) if (ip != null && ip.contains(",")) { ip = ip.split(",")[0].trim(); } return ip; } }
然后把这个过滤器注册到认证服务器的安全配置里:
@EnableAuthorizationServer @Configuration public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private IpWhitelistFilter ipWhitelistFilter; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security // 添加自定义IP过滤器到token端点的认证流程中 .addTokenEndpointAuthenticationFilter(ipWhitelistFilter) // 保留客户端认证要求 .checkTokenAccess("isAuthenticated()") .tokenKeyAccess("permitAll()"); // 其他原有配置... } // 你的其他配置方法... }
一些关键注意事项:
- 如果你用了反向代理(比如Nginx),一定要确保代理服务器传递了真实的客户端IP(比如设置
X-Forwarded-For头),同时在Spring配置中开启server.use-forward-headers=true,否则过滤器拿到的会是代理服务器的IP。 - 不要去掉
isAuthenticated()规则,因为client_credentials模式下,资源服务器调用/oauth/check_token时需要携带客户端的认证信息(比如Basic Auth),这个规则能防止白名单内的未认证请求访问端点。 - 方案2的配置文件方式更适合生产环境,不需要修改代码就能更新白名单IP。
内容的提问来源于stack exchange,提问作者Sameersharma9




