HTML本身不能触发403,必须由服务器或后端框架返回403状态码并响应自定义HTML;直接访问403.html是200,因无权限校验;Nginx需用error_page配合internal防止绕过;Spring Security需配置accessDeniedPage或Controller处理。

直接放结论:HTML 本身不能“做”403权限不足页面,它只是被服务器返回的静态内容;真正的403响应必须由 Web 服务器(如 Nginx、Apache)或后端框架(如 Spring Security、Django)主动触发并返回 403 Forbidden 状态码,再把你的 HTML 文件作为响应体发给浏览器。
为什么直接访问 403.html 会变成 200 而不是 403?
这是最常被误解的一点。如果你把 403.html 放在网站根目录下,然后在浏览器里手动输入 https://yoursite.com/403.html,服务器默认会以 200 OK 返回它——因为这只是普通文件请求,没触发任何权限校验逻辑。
换句话说:403.html 不是“开关”,而是“提示板”。它只在服务器决定拒绝访问时才被亮出来。
- 你访问
/admin,后端检查 session 发现没登录 → 主动返回403+ 渲染403.html - 你请求
/secret/config.json,Nginx 检查到该路径被deny all→ 直接返回403+ 静态403.html - 你直接打开
/403.html→ 服务器当作普通资源返回,状态码是200
Nginx 中怎么让 403 错误显示自定义 HTML 页面?
关键配置项是 error_page,它必须放在 http、server 或 location 块里,且路径要能被 Nginx 读取(注意权限和 root 设置)。
立即学习“前端免费学习笔记(深入)”;
server {
listen 80;
server_name example.com;
root /var/www/html;
<pre class='brush:php;toolbar:false;'># 必须加这一行,否则 403 仍显示默认页
error_page 403 /403.html;
# 确保 /403.html 可被公开访问(不被 deny 拦截)
location = /403.html {
internal; # ← 这行很重要!防止用户直接 GET 它
root /var/www/html;
}}
注意两个易错点:
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
-
error_page 403 /403.html中的路径是 URI 路径(以/开头),不是文件系统路径 -
internal表示该 location 只能被 Nginx 内部跳转调用,用户无法直接访问/403.html—— 否则就绕过权限控制了
Spring Security 怎么返回自定义 403 页面?
Spring Boot 项目中,403 通常来自权限校验失败(比如用户没角色却访问了 @PreAuthorize("hasRole('ADMIN')") 接口)。这时不能靠 Nginx,得在 Java 层处理。
推荐方式是配置 accessDeniedPage:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.accessDeniedPage("/403.html"); // ← 注意:这个路径要能被 Spring 的静态资源处理器服务
}同时确保你的 src/main/resources/static/403.html 存在,且没有被其他安全规则拦截(比如别把它写进 .antMatchers("/403.html").denyAll())。
更稳妥的做法是写一个 Controller 返回 403 视图:
@Controller
public class ErrorController {
@GetMapping("/403")
public String accessDenied() {
return "403"; // 对应 templates/403.html(Thymeleaf)或 static/403.html
}
}然后在 configure() 中用 .accessDeniedHandler(new RedirectAccessDeniedHandler("/403")) 替代 accessDeniedPage。
容易被忽略的细节
真正上线时,这几个点最容易翻车:
- 自定义 403 页面里不要引用
/css/app.css这类相对路径资源——如果当前请求路径是/admin/api/data,浏览器会尝试从/admin/api/css/app.css加载,403 又套 403 - Nginx 的
root和location中的root作用域不同,写错会导致403.html找不到而回退到默认错误页 - 前端路由(如 Vue Router history 模式)下,直接刷新
/admin页面可能触发后端 403,但前端又接管了 URL,导致看不到自定义页——得配合后端 fallback 路由或 Nginx 的try_files


















