5.2 Realm 安全域:认证与授权 本节摘要:Realm 是 Tomcat 的身份档案库,负责回答"你是谁"(认证)与"你能干什么"(授权)两个问题。本节拆解一次受保护请求的完整裁决流程,完成"tomcat-users 配账号、web.xml 声明约束"的标准配置闭环,演示 BASIC 与 FORM 两种登录方式的验证方法,并给出生产环境替换 MemoryRealm 的方向。学完你能给应用加一道规范的登录门禁。 5.1 的两道闸负责拦截,但"拦下来之后怎么判断该不该放"需要一位裁判。本节的 Realm 就是那位拿着花名册的裁判——它嵌在容器结构里(常挂在 Engine 或 Host 层),裁决却精准作用于每一个声明了安全约束的应用。
本节摘要:Realm 是 Tomcat 的身份档案库,负责回答"你是谁"(认证)与"你能干什么"(授权)两个问题。本节拆解一次受保护请求的完整裁决流程,完成"tomcat-users 配账号、web.xml 声明约束"的标准配置闭环,演示 BASIC 与 FORM 两种登录方式的验证方法,并给出生产环境替换 MemoryRealm 的方向。学完你能给应用加一道规范的登录门禁。
5.1 的两道闸负责拦截,但"拦下来之后怎么判断该不该放"需要一位裁判。本节的 Realm 就是那位拿着花名册的裁判——它嵌在容器结构里(常挂在 Engine 或 Host 层),裁决却精准作用于每一个声明了安全约束的应用。
浏览器请求了一个受保护的路径,管道上发生的事按序是四步。第一步,容器发现路径命中 web.xml 里声明的安全约束,检查请求是否已携带身份。第二步,没有身份则发起质询:BASIC 方式返回 401 状态加一个质询头,FORM 方式把用户重定向到登录页。第三步,用户提交凭据,容器把它交给 Realm 核对——认证通过后拿到用户所属的角色列表。第四步,容器拿角色列表对照约束里允许的角色集合,交集非空则放行,响应照常穿过管道返回。

最常用的入门组合是 MemoryRealm 加 BASIC 登录。三处配置一唱一和:档案库登记账号角色、应用声明约束与登录方式、引擎挂好档案库。
第一处,tomcat-users 文件登记两个角色两个账号(生产必须改强口令,这里演示用短口令)。
<!-- conf 目录 tomcat-users.xml --> <role rolename="orderadmin"/> <role rolename="viewer"/> <user username="alice" password="S9f2kQz1" roles="orderadmin"/> <user username="bob" password="Wn4pL7xR" roles="viewer"/>
第二处,应用的 web.xml 声明哪些路径归哪个角色、用哪种登录方式。
<!-- 应用 web.xml:保护管理路径 仅管理员角色可进 --> <security-constraint> <web-resource-collection> <web-resource-name>管理后台</web-resource-name> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>orderadmin</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>订单管理</realm-name> </login-config> <security-role> <role-name>orderadmin</role-name> </security-role>
第三处其实默认已配好——server.xml 的 Engine 里挂着 UserDatabaseRealm,它读 tomcat-users 文件。三处齐活,验证。
# 不带凭据:应被拒 401 curl -i http://localhost:8080/order/admin/dashboard | head -n 5 # 带正确凭据:应 200 curl -i -u alice:S9f2kQz1 http://localhost:8080/order/admin/dashboard | head -n 3 # 带错误密码:应 401 curl -i -u bob:wrongpass http://localhost:8080/order/admin/dashboard | head -n 3
HTTP/1.1 401 WWW-Authenticate: Basic realm="订单管理" HTTP/1.1 200 HTTP/1.1 401
三次请求对应裁决流程的三个出口:质询、放行、拒绝。第四种出口用 bob 的正确密码访问也能触发——他有 viewer 角色但不属于 orderadmin,认证通过而授权失败,状态码 403。401 与 403 的分工:前者是"不知道你是谁",后者是"知道你是谁但你不配进"。
BASIC 的弹窗简陋且登出麻烦,产品化的界面用 FORM 方式。web.xml 的 login-config 换成三行,再准备两个页面。
<login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.html</form-login-page> <form-error-page>/login-failed.html</form-error-page> </form-login-config> </login-config>
登录页的表单有三个约定死死的细节:action 固定为容器保留的登录入口,用户名与密码字段名固定,method 必须为提交表单的动词。
<!-- login.html 表单字段名与动作路径是规范约定 不可自创 --> <form method="POST" action="j_security_check"> <input type="text" name="j_username"/> <input type="password" name="j_password"/> <button type="submit">登录</button> </form>
提交后容器截获这两个保留字段交给 Realm,通过则把身份写进会话——之后同一会话的所有受保护请求自动带身份,这正是 4.3 会话配置的用武之地。登出没有对应的标准动作,常见做法是使会话失效。
⚠️ 常见坑:表单字段名抄错一个字母,提交后永远落到错误页且日志毫无线索——容器是按保留字段名截获的,名字对不上它就当普通请求放过去了。对照上面三行检查,九成"FORM 登录不工作"是这个问题。
裁判配齐,下一节见两位后勤官——Listener 广播容器与应用的大小事件,JNDI 把数据源这类资源集中调度。