5.2 Tomcat Realm (安全域) 第五章:Tomcat 高级特性 5.2 Tomcat Realm (安全域) Tomcat Realm,即安全域,是 Tomcat 中负责用户身份验证(Authentication)和授权(Authorization)的关键组件。它定义了 Tomcat 如何验证用户的身份,并决定用户是否被允许访问受保护的资源。在 Web 应用安全体系中,Realm 扮演着连接用户身份信息存储(例如数据库、LDAP 服务器、内存等)与 Tomcat 安全策略执行的桥梁角色。理解和正确配置 Tomcat Realm 对于构建安全的 Web 应用程序至关重要。 5.2.1 Realm 的概念与作用 概念: Realm 在 Tomcat 中代表一个安全策略的应用领域。
Tomcat Realm,即安全域,是 Tomcat 中负责用户身份验证(Authentication)和授权(Authorization)的关键组件。它定义了 Tomcat 如何验证用户的身份,并决定用户是否被允许访问受保护的资源。在 Web 应用安全体系中,Realm 扮演着连接用户身份信息存储(例如数据库、LDAP 服务器、内存等)与 Tomcat 安全策略执行的桥梁角色。理解和正确配置 Tomcat Realm 对于构建安全的 Web 应用程序至关重要。
概念:
Realm 在 Tomcat 中代表一个安全策略的应用领域。它是一个接口,Tomcat 通过 Realm 接口与特定的用户和角色信息存储库进行交互。当一个受保护的资源被请求时,Tomcat 会调用配置的 Realm 来验证用户的身份,并获取用户的角色信息。
作用:
身份验证 (Authentication): Realm 负责验证用户的身份。当用户尝试访问受保护的资源时,Realm 会根据配置的身份验证机制(例如用户名/密码、证书等)来验证用户提供的凭据是否有效。
授权 (Authorization): 在用户身份验证成功后,Realm 还会提供用户的角色信息。Tomcat 可以根据这些角色信息,结合 Web 应用的 <security-constraint> 配置,来决定用户是否有权限访问特定的资源。
用户和角色管理抽象: Realm 将 Tomcat 与具体的用户和角色数据源解耦。Tomcat 自身并不直接管理用户和角色数据,而是委托给配置的 Realm。这使得 Tomcat 可以灵活地支持多种用户和角色信息存储方式,例如:
内存 (MemoryRealm): 将用户和角色信息存储在 Tomcat 的内存中,适用于开发测试或小型应用。
JDBC 数据库 (JDBCRealm, DataSourceRealm): 从关系型数据库中读取用户和角色信息,适用于需要持久化存储用户信息的场景。
JNDI 目录服务 (JNDIRealm): 从 JNDI 目录服务(如 LDAP)中获取用户和角色信息,适用于企业级环境,可以与现有的身份管理系统集成。
UserDatabaseRealm: 使用 Tomcat 内置的 UserDatabase 组件进行用户和角色管理,数据存储在 XML 文件中。
自定义 Realm: 允许开发者根据特定需求,实现自定义的 Realm 组件,例如连接到特定的身份验证服务或使用自定义的用户信息存储格式。
Realm 在 Tomcat 安全架构中的位置:
流程解释:
用户请求 (User Request): 用户发起对 Web 应用程序资源的请求。
Tomcat Servlet 容器 (Tomcat Servlet Container): Tomcat 接收到请求并进行处理。
安全约束检查 (Security Constraint Check): Tomcat 检查请求的资源是否受到安全约束的保护 (通常在 web.xml 中定义)。
受保护资源 (Yes, Protected Resource): 如果资源受到保护,则进入 Realm 调用流程。
公共资源 (No, Public Resource): 如果资源是公共的,则直接提供服务。
Realm 调用 (Realm Invocation): Tomcat 调用配置的 Realm 组件进行身份验证和授权。
身份验证 (Authentication): Realm 根据配置的机制验证用户提供的凭据。
成功 (Success): 身份验证成功,Realm 返回用户信息和角色信息。进入授权阶段。
失败 (Failure): 身份验证失败,Realm 返回失败信息。Tomcat 返回身份验证失败响应 (通常是 401 Unauthorized)。
授权 (Authorization - Role Check): Tomcat 根据 Realm 返回的用户角色信息,以及 Web 应用的安全约束配置,检查用户是否具有访问资源的权限。
成功 (Success): 用户拥有权限,进入资源服务阶段。
失败 (Failure): 用户没有权限,Tomcat 返回授权失败响应 (通常是 403 Forbidden)。
资源服务 (Resource Served...):
资源服务 (Authentication & Authorization Success): 在身份验证和授权都成功后,Tomcat 将请求的资源返回给用户。
资源直接服务 (Public Resource): 对于公共资源,Tomcat 直接返回资源。
失败响应 (Authentication/Authorization Failure Response): 如果身份验证或授权失败,Tomcat 返回相应的错误响应给用户。
Tomcat 提供了多种内置的 Realm 实现,以适应不同的应用场景。下面详细介绍几种常用的 Realm 类型,并提供配置示例和代码实践。
描述:
org.apache.catalina.realm.MemoryRealm 是最简单的 Realm 实现,它将用户、角色和密码信息存储在 Tomcat 的内存中。数据在 Tomcat 启动时从 conf/tomcat-users.xml 文件中加载。
适用场景:
开发和测试环境: MemoryRealm 配置简单,易于使用,非常适合在开发和测试阶段快速搭建安全环境。
小型应用或演示: 对于用户数量较少,安全性要求不高的简单应用,MemoryRealm 也可以考虑使用。
配置:
在 conf/server.xml 文件中配置 <Realm> 元素,指定 className 为 org.apache.catalina.realm.MemoryRealm。
<Server port="8005" shutdown="SHUTDOWN"> <!-- ... 其他配置 ... --> <Service name="Catalina"> <!-- ... 其他配置 ... --> <Engine name="Catalina" defaultHost="localhost"> <!-- ... 其他配置 ... --> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Context path="/mywebapp" docBase="mywebapp"> <Realm className="org.apache.catalina.realm.MemoryRealm" /> </Context> </Host> </Engine> </Service> </Server>
conf/tomcat-users.xml 示例:
<?xml version="1.0" encoding="UTF-8"?> <tomcat-users xmlns="http://tomcat.apache.org/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"> <role rolename="manager-gui"/> <role rolename="admin-gui"/> <user username="user1" password="password1" roles="manager-gui"/> <user username="admin" password="adminpassword" roles="admin-gui,manager-gui"/> </tomcat-users>
代码实践:
配置 server.xml 和 tomcat-users.xml 如上所示。
在 Web 应用的 web.xml 中配置安全约束,例如:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <security-constraint> <web-resource-collection> <web-resource-name>Protected Area</web-resource-name> <url-pattern>/admin/*</url-pattern> <url-pattern>/manager/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin-gui</role-name> <role-name>manager-gui</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>MyWebAppRealm</realm-name> </login-config> <security-role> <role-name>admin-gui</role-name> </security-role> <security-role> <role-name>manager-gui</role-name> </security-role> </web-app>
部署 Web 应用并尝试访问 /admin/ 或 /manager/ 下的资源。 Tomcat 将会弹出 Basic 认证对话框,使用 tomcat-users.xml 中配置的用户进行登录。
MemoryRealm 工作原理图:
描述:
org.apache.catalina.realm.JDBCRealm 使用 JDBC 连接到关系型数据库,从数据库中读取用户、角色和密码信息。
适用场景:
需要持久化用户信息的 Web 应用: 适用于大部分需要用户注册、登录和权限管理的 Web 应用。
与现有数据库集成: 可以方便地与已有的用户数据库集成,无需额外的数据存储方案。
配置:
在 conf/server.xml 或 context.xml 中配置 <Realm> 元素,指定 className 为 org.apache.catalina.realm.JDBCRealm,并配置数据库连接信息和 SQL 查询语句。
conf/server.xml 示例:
<Server port="8005" shutdown="SHUTDOWN"> <!-- ... 其他配置 ... --> <Service name="Catalina"> <!-- ... 其他配置 ... --> <Engine name="Catalina" defaultHost="localhost"> <!-- ... 其他配置 ... --> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Context path="/mywebapp" docBase="mywebapp"> <Realm className="org.apache.catalina.realm.JDBCRealm" driverName="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC" connectionName="dbuser" connectionPassword="dbpassword" userTable="users" userNameCol="username" userCredCol="password" userRoleTable="user_roles" roleNameCol="role_name" userRolesCol="username" digest="SHA-256" /> </Context> </Host> </Engine> </Service> </Server>
数据库表结构示例 (MySQL):
CREATE TABLE users ( username VARCHAR(50) PRIMARY KEY, password VARCHAR(255) NOT NULL ); CREATE TABLE user_roles ( username VARCHAR(50) NOT NULL, role_name VARCHAR(50) NOT NULL, PRIMARY KEY (username, role_name), FOREIGN KEY (username) REFERENCES users(username) ); INSERT INTO users (username, password) VALUES ('user1', SHA2('password1', 256)); INSERT INTO users (username, password) VALUES ('admin', SHA2('adminpassword', 256)); INSERT INTO user_roles (username, role_name) VALUES ('user1', 'user'); INSERT INTO user_roles (username, role_name) VALUES ('admin', 'admin'); INSERT INTO user_roles (username, role_name) VALUES ('admin', 'manager');
代码实践:
创建数据库和表结构 (如上所示)。
配置 server.xml 中的 JDBCRealm,确保数据库连接信息和 SQL 查询语句正确配置。
在 Web 应用的 web.xml 中配置安全约束和角色 (与 MemoryRealm 示例类似,但角色名需要与数据库中的角色名一致)。
部署 Web 应用并尝试访问受保护资源。 Tomcat 将会使用 JDBCRealm 连接数据库,验证用户身份并获取角色信息。
JDBCRealm 工作原理图:
描述:
org.apache.catalina.realm.DataSourceRealm 与 JDBCRealm 类似,也使用数据库进行用户和角色信息的存储。但 DataSourceRealm 使用 JNDI 数据源 (DataSource) 来获取数据库连接,而不是直接配置 JDBC 连接信息。
适用场景:
JNDI 数据源环境: 适用于应用服务器环境中,通常由容器管理数据库连接池,通过 JNDI 查找数据源。
更灵活的数据库连接管理: 使用 JNDI 数据源可以实现更灵活的数据库连接管理,例如连接池配置、数据源切换等。
配置:
配置 JNDI 数据源: 在 Tomcat 的 conf/context.xml 或 conf/server.xml 中配置 <Resource> 元素来定义 JNDI 数据源。例如:
conf/context.xml 示例:
<Context> <!-- ... 其他配置 ... --> <Resource name="jdbc/mydb" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC" username="dbuser" password="dbpassword" maxActive="20" maxIdle="10" maxWait="-1"/> </Context>
配置 DataSourceRealm: 在 conf/server.xml 或 context.xml 中配置 <Realm> 元素,指定 className 为 org.apache.catalina.realm.DataSourceRealm,并引用 JNDI 数据源名称。
conf/server.xml 示例:
<Server port="8005" shutdown="SHUTDOWN"> <!-- ... 其他配置 ... --> <Service name="Catalina"> <!-- ... 其他配置 ... --> <Engine name="Catalina" defaultHost="localhost"> <!-- ... 其他配置 ... --> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Context path="/mywebapp" docBase="mywebapp"> <Realm className="org.apache.catalina.realm.DataSourceRealm" dataSourceName="jdbc/mydb" userTable="users" userNameCol="username" userCredCol="password" userRoleTable="user_roles" roleNameCol="role_name" userRolesCol="username" digest="SHA-256" /> </Context> </Host> </Engine> </Service> </Server>
代码实践:
配置 JNDI 数据源 (如上所示)。
配置 server.xml 或 context.xml 中的 DataSourceRealm,引用 JNDI 数据源名称。
数据库表结构和 web.xml 配置与 JDBCRealm 示例类似。
部署 Web 应用并测试。 Tomcat 将通过 JNDI 数据源获取数据库连接,进行用户身份验证和角色获取。
DataSourceRealm 工作原理图:
描述:
org.apache.catalina.realm.JNDIRealm 使用 JNDI 目录服务 (例如 LDAP) 来进行用户身份验证和角色查询。
适用场景:
企业级环境,集成 LDAP 或其他目录服务: 适用于需要与企业现有的身份管理系统 (如 Active Directory, OpenLDAP) 集成的场景。
集中式用户和角色管理: 可以利用目录服务的集中式用户管理能力,简化 Web 应用的安全配置。
配置:
在 conf/server.xml 或 context.xml 中配置 <Realm> 元素,指定 className 为 org.apache.catalina.realm.JNDIRealm,并配置 LDAP 连接信息和查询参数。
conf/server.xml 示例 (连接到 OpenLDAP):
<Server port="8005" shutdown="SHUTDOWN"> <!-- ... 其他配置 ... --> <Service name="Catalina"> <!-- ... 其他配置 ... --> <Engine name="Catalina" defaultHost="localhost"> <!-- ... 其他配置 ... --> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Context path="/mywebapp" docBase="mywebapp"> <Realm className="org.apache.catalina.realm.JNDIRealm" connectionURL="ldap://localhost:389" connectionName="cn=admin,dc=example,dc=org" connectionPassword="adminpassword" userBase="ou=users,dc=example,dc=org" userSearch="(uid={0})" userRoleName="ou=roles,dc=example,dc=org" roleNameAttribute="cn" roleSearch="(&(objectClass=groupOfNames)(member=uid={0},ou=users,dc=example,dc=org))" digest="SHA-256" /> </Context> </Host> </Engine> </Service> </Server>
LDAP 目录结构示例 (OpenLDAP):
dc=example,dc=org |-- ou=users | |-- uid=user1,ou=users,dc=example,dc=org | | |-- objectClass: inetOrgPerson | | |-- uid: user1 | | |-- cn: User One | | |-- sn: One | | |-- userPassword: {SHA-256}hashed_password_user1 | |-- uid=admin,ou=users,dc=example,dc=org | | |-- objectClass: inetOrgPerson | | |-- uid: admin | | |-- cn: Admin User | | |-- sn: Admin | | |-- userPassword: {SHA-256}hashed_password_admin |-- ou=roles | |-- cn=admin-role,ou=roles,dc=example,dc=org | | |-- objectClass: groupOfNames | | |-- cn: admin-role | | |-- member: uid=admin,ou=users,dc=example,dc=org | |-- cn=manager-role,ou=roles,dc=example,dc=org | | |-- objectClass: groupOfNames | | |-- cn: manager-role | | |-- member: uid=admin,ou=users,dc=example,dc=org | | |-- member: uid=user1,ou=users,dc=example,dc=org
代码实践:
搭建 LDAP 服务器 (例如 OpenLDAP) 并配置用户和角色信息 (如上所示)。
配置 server.xml 或 context.xml 中的 JNDIRealm,确保 LDAP 连接信息和查询参数正确配置,以匹配 LDAP 目录结构。
在 Web 应用的 web.xml 中配置安全约束和角色 (角色名需要与 LDAP 目录中的角色名一致)。
部署 Web 应用并测试。 Tomcat 将使用 JNDIRealm 连接 LDAP 服务器,验证用户身份并获取角色信息。
JNDIRealm 工作原理图:
描述:
org.apache.catalina.realm.UserDatabaseRealm 使用 Tomcat 内置的 UserDatabase 组件进行用户和角色管理。用户和角色信息存储在 XML 文件中,默认是 conf/tomcat-users.xml (与 MemoryRealm 相同,但 UserDatabaseRealm 可以独立配置)。
适用场景:
类似于 MemoryRealm,但可以独立管理用户数据库: 适用于需要文件存储用户信息,但又不想与 MemoryRealm 共享 tomcat-users.xml 的场景。
简单的文件型用户管理: 可以作为一种简单的文件型用户管理方案,无需数据库或 LDAP 服务。
配置:
配置 UserDatabase 组件: 在 conf/server.xml 中配置 <UserDatabase> 元素,指定用户数据库的资源名称和存储路径 (可选)。
<Server port="8005" shutdown="SHUTDOWN"> <!-- ... 其他配置 ... --> <Service name="Catalina"> <!-- ... 其他配置 ... --> <Engine name="Catalina" defaultHost="localhost"> <!-- ... 其他配置 ... --> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Context path="/mywebapp" docBase="mywebapp"> </Context> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Host> </Engine> <Resources> <UserDatabase id="UserDatabase" pathname="conf/my-tomcat-users.xml"/> <!-- 自定义用户数据文件 --> </Resources> </Service> </Server>
创建用户数据文件 (conf/my-tomcat-users.xml 示例):
<?xml version="1.0" encoding="UTF-8"?> <tomcat-users xmlns="http://tomcat.apache.org/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"> <role rolename="webapp-user"/> <user username="webappuser" password="webapppassword" roles="webapp-user"/> </tomcat-users>
代码实践:
配置 server.xml 中的 UserDatabase 和 UserDatabaseRealm,指定自定义的用户数据文件路径。
创建用户数据文件 (如 conf/my-tomcat-users.xml) 并配置用户和角色信息.
在 Web 应用的 web.xml 中配置安全约束和角色 (角色名需要与用户数据文件中的角色名一致)。
部署 Web 应用并测试。 Tomcat 将使用 UserDatabaseRealm 从指定的用户数据文件中加载用户和角色信息。
UserDatabaseRealm 工作原理图:
描述:
Tomcat 允许开发者根据特定需求,实现自定义的 Realm 组件。这可以通过继承 org.apache.catalina.realm.RealmBase 类或实现 org.apache.catalina.Realm 接口来完成。
适用场景:
集成特殊的身份验证系统: 例如,连接到自定义的身份验证服务、使用特定的加密算法、或集成第三方认证服务 (OAuth 2.0, OpenID Connect 等)。
定制化的用户和角色管理: 例如,从 NoSQL 数据库、云服务或其他非标准数据源获取用户和角色信息。
复杂的安全策略实现: 例如,实现基于属性的访问控制 (Attribute-Based Access Control, ABAC) 或其他高级安全策略。
实现自定义 Realm 的基本步骤:
创建 Java 类,继承 org.apache.catalina.realm.RealmBase 或实现 org.apache.catalina.Realm 接口。
重写或实现以下关键方法:
authenticate(String username, String credentials, String method, String digest, String clientCertChain, String authType, String realmName): 执行用户身份验证,验证用户名和密码。
getRoles(Principal principal): 获取用户的角色信息。
hasRole(Principal principal, String role): 检查用户是否拥有特定角色。
startInternal() 和 stopInternal(): Realm 的启动和停止生命周期方法,用于初始化和清理资源。