服务器无法通过URL直接访问WEB-INF下的文件夹,但可以通过Servlet转发、Spring MVC内部映射或修改部署配置等方式实现安全访问。WEB-INF目录是Java Web应用的保护屏障,浏览器请求会被容器拦截并返回404,本文梳理了主流的几种绕过方案、配置差异以及不同服务器下的适配细节,帮你按场景选对路子。
WEB-INF目录为什么不能直接访问
Tomcat、Jetty等主流Servlet容器默认将WEB-INF视为受保护区域,放在这个目录下的文件,比如/WEB-INF/views/index.jsp,本质上属于服务端资源,容器的DefaultServlet在收到静态资源请求时,会先检查请求路径是否包含WEB-INF前缀,一旦命中就直接响应404,压根不会读取磁盘上的文件。
行业共识认为,这种设计的初衷是防止JSP源码、配置文件、数据库连接参数被外部嗅探,如果强行用<location>映射或符号链接暴露该目录,配置错误会让web.xml、application.properties、.class文件变成可下载的明文资产,曾经有安全排查报告显示,部分中小企业站点因为开放WEB-INF目录导致jdbc.properties泄露,攻击者借此直连内网数据库,所以直接改容器配置去解开这个目录,等于引狼入室。
WEB-INF下文件怎么访问:按场景选方案
Servlet做内部转发,URL上不暴露路径
这是最规范的玩法,你的Servlet代码里用RequestDispatcher跳转到WEB-INF内的页面,浏览器地址栏只显示/servlet/showUser,用户压根不知道真实文件路径。
@WebServlet("/admin/user")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
req.getRequestDispatcher("/WEB-INF/pages/userList.jsp").forward(req, resp);
}
}
关键动作是forward,不是sendRedirect。forward在服务端完成资源加载,浏览器零感知;sendRedirect会发起第二次HTTP请求,一旦第二次请求的URL指向WEB-INF,容器照样拦你,统计下来,不少新手卡在这一步,把转发和重定向混为一谈。
如果你用的还是老旧的web.xml配置方式,注册Servlet时这样写:
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>com.demo.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/admin/user</url-pattern>
</servlet-mapping>
请求路径是/admin/user,Servlet内部再钻进WEB-INF取文件,外面的人永远摸不到真实的jsp路径。
Spring MVC的InternalResourceViewResolver
Spring Boot项目里最常见的做法是配置视图解析器,让Controller返回的字符串自动拼接上WEB-INF的前缀和后缀,在
application.properties里加这段:
spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp
Controller返回"userList"时,框架自动去/WEB-INF/views/userList.jsp找文件,这种做法的隐藏效果是,URL干净且不带WEB-INF字样,用户看到了/user/list就真的以为有list.do这么个接口,此处有个细节要记牢:视图解析器只管返回ModelAndView时拼接路径,如果你在Controller里直接返回一个RedirectView指向WEB-INF,那是必然失败的,By the way,Spring Boot如果打包成Jar运行,WEB-INF目录下的JSP会有兼容性问题,这种情况建议改用Thymeleaf模板引擎,路径指向templates目录。
Servlet转发WEB-INF页面与直接暴露目录的差异
差异核心在安全边界和资源语义上,类似场景的工作中,转发方式下WEB-INF里的JSP只能被服务端调用,即便有人猜到/WEB-INF/secret.jsp也拿不到内容;而直接暴露目录虽然让浏览器能直连文件,但代价是网页静态资源的引用路径、JS里的相对路径全部乱套。
| 对比维度 | Servlet转发 | 直接暴露目录 |
|---|---|---|
| URL是否暴露真实路径 | 不暴露 | 暴露 |
| 容器默认支持 | 完全支持 | 需要额外配置 |
| 安全风险 | 低 | 高,可能泄露配置 |
| 资源缓存控制 | 服务端自由控制 | 走容器默认策略 |
| 适合场景 | 后台管理、业务页面 | 极少,多数是历史遗留问题 |
Spring Controller直接返回视图名
不使用InternalResourceViewResolver时,你依然可以写裸的Controller代码:
@Controller
public class PageController {
@RequestMapping("/dashboard")
public String dashboard() {
return "/WEB-INF/pages/dashboard.jsp";
}
}
前提是你自己装配了ViewResolver,或者继承WebMvcConfigurerAdapter做了自定义配置,这种写法灵活,但每个方法都要写全路径,比较啰嗦,对比之下,配好InternalResourceViewResolver一劳永逸,团队协作时其他人只需要记住return "页面名"就行,不容易犯错。
web.xml配置直接放行WEB-INF的玩法与风险
有一个野路子是拿DefaultServlet的映射去接WEB-INF的请求:
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/WEB-INF/</url-pattern>
</servlet-mapping>
这段配置一上,Tomcat会把WEB-INF当成普通静态资源目录,浏览器直接输入/WEB-INF/pages/a.jsp就能看到源码,业内专家指出,这种做法相当于拆掉了防护墙,且会让IDE里的项目路径和运行时路径错位,引发资源重复加载,沿用这种配置的团队通常是为了图省事,或者接手了老项目,里面的<url-pattern>写成了导致其他Servlet全部失效。
如果你非要在开发环境临时看看WEB-INF里的页面效果,建议用IDE的“Open in Browser”功能,或者把文件临时复制到webapp/static下,绝对不要为了本地调试把这段配置留在生产环境的web.xml里,一时的省事可能变成一场数据泄露事故,正规做法是开发时用spring.profiles.active=dev加载另一个配置文件,将视图解析器指到/static/,生产环境则指回/WEB-INF/。
常见服务器中WEB-INF路径访问的适配要点
Tomcat
Tomcat 8.5以上的DefaultServlet有个readOnly参数,默认true即禁止写入,GET请求被默认拒绝,PUT和DELETE同样没戏,想精确控制某个Servlet的映射时,建议别碰WEB-INF本身,而是给WEB-INF外的子目录单独建映射,另外Tomcat的Context.xml里可以配置<Resources>标签,用WebResourceRoot给WEB-INF加别名,这种做法工程量大,收益却很小,多数情况没必要。
Jetty
Jetty对WEB-INF的保护逻辑与Tomcat一致,但它的WebAppContext有setCopyWebDir和setCopyWebInf两个开关,如果开启复制,WEB-INF里的内容会被复制到临时目录,可能引发JSP修改后不生效的问题,排查时先确认tmpDir下是否存在旧的JSP缓存,清了再谈其他。
Spring Boot内嵌服务器
Spring Boot的spring-boot-starter-web内嵌Tomcat时,WEB-INF路径是classpath的一部分,此时直接访问/WEB-INF/同样被拦截,如果你用application.yml配置了server.tomcat.additional-tld-skip-patterns等参数,跟WEB-INF访问权限没有关系,别被误导。
Nginx反代场景
如果你用Nginx做前端代理,想绕过Java容器的限制,可以在Nginx层做try_files把请求抛给后端某个Controller,由Controller转发到WEB-INF,这类架构下,Nginx的location规则不要写/WEB-INF,直接在upstream指向后端应用的Servlet路径即可,常见的错误是Nginx配置了root /opt/app/webapp;然后直接location /WEB-INF/ { alias /opt/app/webapp/WEB-INF/; },这会让Nginx越权直接读文件,后端权限控制形同虚设,正确思路是:Nginx只负责负载均衡,所有WEB-INF的访问请求一律回源到Java层处理。
WEB-INF文件夹访问失败的常见排查路径
- 确认是404错误还是
500错误
,404说明请求被容器拦截或路径错误,500说明转发逻辑出错,比如JSP编译异常。 - 检查项目是否打成了Fat Jar,Spring Boot的Fat Jar里WEB-INF结构已经被Spring Boot的Loader改写,路径不能用传统方式硬编码。
- 用浏览器的“查看源代码”确认响应体是否为JSP源码,如果看到的是源码,说明容器绕过Servlet直接把文件吐了出去,优先级最高的
web.xml映射和DefaultServlet配置存在问题。 - 查看
WEB-INF/classes下的.class文件时间戳是否正常,实践中出现过改了Java代码但JSP引用的Bean还是旧字节码的情况,导致转发后页面报错。
Q&A:服务器访问WEB-INF文件夹的常见问题
JSP放在WEB-INF下但CSS和JS无法加载怎么办?
把CSS、JS、图片等静态资源放在webapp/static或webapp/resources目录,JSP里用${pageContext.request.contextPath}/static/css/style.css引用,WEB-INF下的JSP里写相对路径../static/css在转发时会错位,因为浏览器地址栏的URL是Servlet映射路径,不是JSP物理路径,使用绝对路径或JSTL的<c:url>标签也能一劳永逸解决。
非Java项目(比如Node.js或PHP)能不能访问到WEB-INF?
WEB-INF是Jakarta EE的规范目录,Node.js的Express或PHP的Apache默认不识别这个目录名,这时候WEB-INF只是一个普通文件夹,可以通过静态中间件直接访问,但从安全角度,如果你在混合架构里让Nginx指向了Java项目的webapp目录,Nginx会忽略WEB-INF的Java容器保护逻辑,直接吐出文件内容,混合部署时注意别把Java项目根目录直接配给Nginxroot指令,否则保护机制形同虚设。
有没有办法让WEB-INF内的文件支持断点续传?
WEB-INF本身不承载静态资源语义,所以标准Servlet容器不会为它生成ETag和Accept-Ranges响应头,想实现断点续传就得自写Servlet,用RandomAccessFile配合Range头手动处理,工作量和收益不成正比,多数情况下把大文件挪到WEB-INF外更符合常规做法。
回到核心结论:服务器访问WEB-INF下的文件夹没有银弹,正路是Servlet转发或Spring MVC视图解析,野路子是容器配置直接放行但负担安全风险,实际项目中优先让所有对WEB-INF的访问都经过Java层代码,这样能拿到权限控制、参数校验、日志审计三个好处,配置层面,Tomcat和Jetty上别动DefaultServlet的WEB-INF拦截逻辑,让目录保持封闭,如果你正在处理老项目的迁移,先把JSP挪到正规视图层,再用Spring的控制器接住转发,迁移完成后删除对WEB-INF的直接引用,用扫描工具确认没有/WEB-INF/的URL映射残留,这条路线走完,WEB-INF就不再是访问路上的绊脚石了。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/610235.html




