在Servlet/Filter中如何获取web.xml中无auth-constraint的URL模式列表?
获取无auth-constraint的security-constraint对应URL列表的方法
这个问题我之前也琢磨过,Servlet标准API并没有直接提供读取security-constraint配置的接口,不过有两种可行的思路,一种是跨容器的通用XML解析方式,另一种是借助容器特定API快速获取,下面给你详细拆解:
一、通用跨容器方式:手动解析部署描述符
这种方式不依赖具体容器,能在Tomcat、Jetty、GlassFish等所有Servlet容器中运行,核心是自己解析web.xml以及可能存在的web-fragment.xml文件,筛选出没有配置auth-constraint的security-constraint下的url-pattern。
实现步骤(以Filter为例)
在Filter的init方法中完成解析(只执行一次,避免重复解析影响性能):
- 获取
web.xml的输入流,同时处理WEB-INF/lib目录下jar包中的web-fragment.xml - 用XML解析工具(比如DOM)遍历所有
security-constraint元素 - 判断每个
security-constraint是否包含auth-constraint,如果没有则提取对应的url-pattern
示例代码
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import org.w3c.dom.*; import javax.xml.parsers.*; import java.io.*; import java.util.*; import java.util.jar.*; @WebFilter(filterName = "UnprotectedUrlFilter", urlPatterns = "/*") public class UnprotectedUrlFilter implements Filter { private List<String> unprotectedUrlPatterns; @Override public void init(FilterConfig filterConfig) throws ServletException { unprotectedUrlPatterns = new ArrayList<>(); ServletContext context = filterConfig.getServletContext(); // 解析主web.xml文件 parseDeploymentDescriptor(context.getResourceAsStream("/WEB-INF/web.xml")); // 解析WEB-INF/lib中jar包的web-fragment.xml Set<String> libResources = context.getResourcePaths("/WEB-INF/lib/"); if (libResources != null) { for (String libPath : libResources) { if (libPath.endsWith(".jar")) { try (JarInputStream jarStream = new JarInputStream(context.getResourceAsStream(libPath))) { JarEntry entry; while ((entry = jarStream.getNextJarEntry()) != null) { if ("META-INF/web-fragment.xml".equals(entry.getName())) { parseDeploymentDescriptor(jarStream); break; } } } catch (IOException e) { // 这里可以根据业务需求做异常处理,比如打日志 e.printStackTrace(); } } } } // 将解析结果存储到ServletContext,方便后续使用 context.setAttribute("unprotectedUrls", unprotectedUrlPatterns); } private void parseDeploymentDescriptor(InputStream inputStream) { if (inputStream == null) return; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(inputStream); doc.getDocumentElement().normalize(); // 遍历所有security-constraint元素 NodeList constraintNodes = doc.getElementsByTagName("security-constraint"); for (int i = 0; i < constraintNodes.getLength(); i++) { Node constraintNode = constraintNodes.item(i); if (constraintNode.getNodeType() == Node.ELEMENT_NODE) { Element constraintElement = (Element) constraintNode; // 检查当前security-constraint是否包含auth-constraint NodeList authConstraintNodes = constraintElement.getElementsByTagName("auth-constraint"); if (authConstraintNodes.getLength() == 0) { // 提取所有url-pattern NodeList urlPatternNodes = constraintElement.getElementsByTagName("url-pattern"); for (int j = 0; j < urlPatternNodes.getLength(); j++) { String urlPattern = urlPatternNodes.item(j).getTextContent().trim(); unprotectedUrlPatterns.add(urlPattern); } } } } } catch (ParserConfigurationException | SAXException | IOException e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 这里可以使用unprotectedUrlPatterns做你需要的特定操作 chain.doFilter(request, response); } @Override public void destroy() { // 清理资源 unprotectedUrlPatterns.clear(); } }
注意:要区分“没有auth-constraint元素”和“有auth-constraint但未配置角色”两种情况——后者实际上是拒绝所有用户访问,不属于无保护的URL,解析时要注意判断条件。
二、容器特定方式:利用容器内部API
如果你的应用固定运行在某个Servlet容器(比如Tomcat),可以直接调用容器提供的内部API来获取security-constraint配置,这种方式更简洁,但跨容器兼容性差。
以Tomcat为例的示例代码
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import org.apache.catalina.core.ApplicationContextFacade; import org.apache.catalina.core.StandardContext; import org.apache.catalina.deploy.SecurityConstraint; import java.lang.reflect.Field; import java.util.*; @WebFilter(filterName = "TomcatUnprotectedUrlFilter", urlPatterns = "/*") public class TomcatUnprotectedUrlFilter implements Filter { private List<String> unprotectedUrlPatterns; @Override public void init(FilterConfig filterConfig) throws ServletException { unprotectedUrlPatterns = new ArrayList<>(); ServletContext context = filterConfig.getServletContext(); // 借助反射获取Tomcat的StandardContext if (context instanceof ApplicationContextFacade) { try { Field contextField = ApplicationContextFacade.class.getDeclaredField("context"); contextField.setAccessible(true); StandardContext standardContext = (StandardContext) contextField.get(context); // 获取所有security-constraint配置 SecurityConstraint[] constraints = standardContext.findConstraints(); for (SecurityConstraint constraint : constraints) { // Tomcat中,没有auth-constraint的SecurityConstraint的getAuthConstraint()返回null if (constraint.getAuthConstraint() == null) { String[] urlPatterns = constraint.findUrlPatterns(); unprotectedUrlPatterns.addAll(Arrays.asList(urlPatterns)); } } } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } context.setAttribute("unprotectedUrls", unprotectedUrlPatterns); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 执行你的特定操作 chain.doFilter(request, response); } @Override public void destroy() { unprotectedUrlPatterns.clear(); } }
注意:这种方式依赖Tomcat的内部实现,版本升级可能导致API变化,使用前要做好兼容性测试;其他容器(如Jetty)的API会不同,需要单独适配。
内容的提问来源于stack exchange,提问作者Mohamed_bilal
相关产品推荐
相关产品推荐

