Apache Shiro 构建并运行示例应用指南
Apache Shiro 2.2.1 构建并运行示例应用指南:从零创建 Maven 工程,给出完整 pom.xml、shiro.ini 与三个示例源码,覆盖登录成功与会话读写、密码错误与账号不存在两类失败场景、角色与权限校验(hasRole、isPermitted、checkPermission),附编译运行命令、真实回显与逐行结果解读,全程实测。
Apache Shiro 构建并运行示例应用指南
目标:从零构建并运行一组基于 Apache Shiro 2.2.1 的最小认证授权示例(共 3 个:登录成功与会话读写、登录失败异常处理、角色与权限校验)。给出完整 pom.xml、shiro.ini、示例 Java 源码、编译与运行命令及真实回显。所有命令与输出均在 Ubuntu 22.04.5 + OpenJDK 17 环境实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS
- Java:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3
- Shiro 版本:2.2.1(依赖 shiro-core,传递引入 lang/cache/crypto/config/event 等模块)
- 日志实现:slf4j-simple 2.0.16 + jcl-over-slf4j 2.0.16
- 示例工程目录:
/root/shiro-demo/ - 演示账号:root、admin、alice(密码统一 q1w2e3r4)
二、前置条件
-
已安装 JDK 17 与 Maven(安装与环境变量验证见《Apache Shiro 安装指南》):
java -version mvn -version -
服务器可访问 Maven Central(首次构建需下载依赖)。
-
示例进程堆内存建议限制在 256–512MB(见「九、注意事项」)。
三、示例工程结构
标准 Maven 布局,一个 pom、两个 ini 配置与 5 个 Java 类:
./pom.xml
./src/main/java/demo/AuthzDemo.java
./src/main/java/demo/ConfigDemo.java
./src/main/java/demo/ConfigDemo2.java
./src/main/java/demo/LoginDemo.java
./src/main/java/demo/LoginFailureDemo.java
./src/main/resources/shiro.ini
./src/main/resources/shiro-tuned.ini
(以上为 find . -type f | sort 实测回显。)
| 文件 | 作用 |
|---|---|
| LoginDemo.java | 示例 1:root 登录成功 + Session 读写(本文第六章) |
| LoginFailureDemo.java | 示例 2:密码错误/账号不存在/登录成功对照(本文第七章) |
| AuthzDemo.java | 示例 3:hasRole / isPermitted / checkPermission 校验(本文第八章) |
| ConfigDemo.java、ConfigDemo2.java、shiro-tuned.ini | 默认组件打印与 [main] 覆盖验证,详见《Apache Shiro 配置参数详解》 |
| shiro.ini | 账号与角色数据(本文第五章) |
创建工程骨架:
mkdir -p /root/shiro-demo/src/main/java/demo /root/shiro-demo/src/main/resources
cd /root/shiro-demo
四、完整 pom.xml
pom 引入 shiro-core 2.2.1,排除其传递的 log4j2 绑定,改用 slf4j-simple,并桥接 jcl-over-slf4j:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>shiro-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>2.2.1</version>
<exclusions>
<!-- shiro-core 2.x 依赖树中的 log4j2 绑定,改用轻量 slf4j-simple 输出日志 -->
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.16</version>
</dependency>
<!-- shiro-config-ogdl 依赖 commons-beanutils,1.11.0 不再传递 commons-logging,
桥接 jcl-over-slf4j 补齐 org.apache.commons.logging.LogFactory -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.16</version>
</dependency>
</dependencies>
</project>
⚠️ 注意:
jcl-over-slf4j不可省略。shiro-core 2.2.1 依赖树中的 commons-beanutils 1.11.0 不再传递 commons-logging,缺失时运行直接报NoClassDefFoundError: org/apache/commons/logging/LogFactory(排障过程见《Apache Shiro 安装指南》常见问题)。
五、shiro.ini 账号与角色
创建 /root/shiro-demo/src/main/resources/shiro.ini:
[main]
# IniSecurityManagerFactory 解析本文件时,依据 [users]/[roles] 隐式构建 IniRealm 并挂到 SecurityManager
[users]
root = q1w2e3r4, admin
admin = q1w2e3r4, user, deployer
alice = q1w2e3r4, user
[roles]
admin = *
user = app:read, app:write
deployer = app:deploy
[users]段:每行「账号 = 密码, 角色1, 角色2」。root 挂 admin 角色;admin 挂 user 与 deployer;alice 仅 user。[roles]段:每行「角色 = 权限列表」。admin 用通配符*表示全部权限;user 仅app:read与app:write;deployer 仅app:deploy。
六、示例 1:登录成功与 Session(LoginDemo)
6.1 示例源码
创建 /root/shiro-demo/src/main/java/demo/LoginDemo.java:
package demo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.lang.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 示例 1:加载 shiro.ini,root 账号登录成功,并演示 Session 基本读写。
*/
public class LoginDemo {
private static final Logger log = LoggerFactory.getLogger(LoginDemo.class);
public static void main(String[] args) {
// 1. 从 classpath:shiro.ini 构建 SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 2. 获取当前 Subject 并登录
Subject currentUser = SecurityUtils.getSubject();
log.info("before login, isAuthenticated = {}", currentUser.isAuthenticated());
UsernamePasswordToken token = new UsernamePasswordToken("root", "q1w2e3r4");
currentUser.login(token);
log.info("after login, isAuthenticated = {}", currentUser.isAuthenticated());
log.info("principal = {}", currentUser.getPrincipal());
log.info("hasRole(admin) = {}", currentUser.hasRole("admin"));
// 3. Session 读写
Session session = currentUser.getSession();
session.setAttribute("loginUser", currentUser.getPrincipal());
log.info("session attribute loginUser = {}", session.getAttribute("loginUser"));
log.info("sessionId = {}, host = {}", session.getId(), session.getHost());
// 4. 退出登录
currentUser.logout();
log.info("after logout, isAuthenticated = {}", currentUser.isAuthenticated());
}
}
6.2 编译与运行
cd /root/shiro-demo
mvn compile
mvn exec:java -Dexec.mainClass=demo.LoginDemo
编译实测回显(末段):
[INFO] Compiling 5 source files to /root/shiro-demo/target/classes
[WARNING] /root/shiro-demo/src/main/java/demo/LoginFailureDemo.java: Some input files use or override a deprecated API.
[WARNING] /root/shiro-demo/src/main/java/demo/LoginFailureDemo.java: Recompile with -Xlint:deprecation for details.
[INFO] BUILD SUCCESS
[INFO] Total time: 1.340 s
[INFO] Finished at: 2026-10-12T12:11:48Z
⚠️ 注意:deprecated 警告来自
IniSecurityManagerFactory(2.x 标记过时),不影响运行。
运行实测回显(应用段与构建结果):
[demo.LoginDemo.main()] INFO demo.LoginDemo - before login, isAuthenticated = false
[demo.LoginDemo.main()] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager - Enabling session validation scheduler...
[demo.LoginDemo.main()] INFO demo.LoginDemo - after login, isAuthenticated = true
[demo.LoginDemo.main()] INFO demo.LoginDemo - principal = root
[demo.LoginDemo.main()] INFO demo.LoginDemo - hasRole(admin) = true
[demo.LoginDemo.main()] INFO demo.LoginDemo - session attribute loginUser = root
[demo.LoginDemo.main()] INFO demo.LoginDemo - sessionId = 901cd5c2-2d05-48f2-904d-21e0dcce612e, host = null
[demo.LoginDemo.main()] INFO demo.LoginDemo - after logout, isAuthenticated = false
[WARNING] thread Thread[SessionValidationThread-1,5,demo.LoginDemo] was interrupted but is still alive after waiting at least 15000msecs
[WARNING] thread Thread[SessionValidationThread-1,5,demo.LoginDemo] will linger despite being asked to die via interruption
[WARNING] NOTE: 1 thread(s) did not finish despite being asked to via interruption. This is not a problem with exec:java, it is a problem with the running code. Although not serious, it should be remedied.
[INFO] BUILD SUCCESS
[INFO] Total time: 15.639 s
6.3 结果解读
before login, isAuthenticated = false→after login, isAuthenticated = true:UsernamePasswordToken 携带 root/q1w2e3r4,IniRealm 依据 shiro.ini[users]段完成认证。principal = root:登录主体即账号名。hasRole(admin) = true:root 在[users]段被授予 admin 角色。session attribute loginUser = root:Session 属性写入后可读回;sessionId为随机 UUID,每次运行不同。after logout, isAuthenticated = false:logout() 清理会话与身份。- 末尾 3 行 WARNING:exec:java 方式下 logout() 后会话校验线程(SessionValidationThread-1)未即时退出的残留提示,不影响功能;用
java -cp直接运行无此现象。
七、示例 2:登录失败场景(LoginFailureDemo)
7.1 示例源码
创建 /root/shiro-demo/src/main/java/demo/LoginFailureDemo.java:
package demo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.lang.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
/**
* 示例 2:三种登录场景 —— 密码错误、账号不存在、凭据正确(对照)。
*/
public class LoginFailureDemo {
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityUtils.setSecurityManager(factory.getInstance());
attempt("root", "wrong-password"); // 用户存在,密码错误
attempt("nobody", "q1w2e3r4"); // 用户不存在
attempt("alice", "q1w2e3r4"); // 凭据正确(对照组)
}
private static void attempt(String username, String password) {
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
System.out.println("LOGIN OK: " + username + ", isAuthenticated = " + subject.isAuthenticated());
subject.logout();
} catch (UnknownAccountException e) {
System.out.println("FAILURE: " + username + " -> UnknownAccountException: " + e.getMessage());
} catch (IncorrectCredentialsException e) {
System.out.println("FAILURE: " + username + " -> IncorrectCredentialsException: " + e.getMessage());
} catch (AuthenticationException e) {
System.out.println("FAILURE: " + username + " -> " + e.getClass().getName() + ": " + e.getMessage());
}
}
}
7.2 运行与回显
方式一(exec:java):
cd /root/shiro-demo && mvn exec:java -Dexec.mainClass=demo.LoginFailureDemo
实测回显(应用段):
FAILURE: root -> IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - root, rememberMe=false] did not match the expected credentials.
FAILURE: nobody -> UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@19091ea8] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - nobody, rememberMe=false].
[demo.LoginFailureDemo.main()] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager - Enabling session validation scheduler...
LOGIN OK: alice, isAuthenticated = true
方式二(java -cp 直接运行,先由 Maven 生成依赖 classpath 清单):
cd /root/shiro-demo
mvn -q dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt
java -cp "target/classes:$(cat /tmp/cp.txt)" demo.LoginFailureDemo
实测回显(slf4j-simple 的线程名为 [main]):
FAILURE: root -> IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - root, rememberMe=false] did not match the expected credentials.
FAILURE: nobody -> UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@7f9a81e8] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - nobody, rememberMe=false].
[main] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager - Enabling session validation scheduler...
LOGIN OK: alice, isAuthenticated = true
💡 说明:
IniRealm@后的 hashcode 每次运行随机变化,属正常现象。
7.3 结果解读
- root + wrong-password:账号存在但密码不匹配 → 抛
IncorrectCredentialsException,异常消息指出提交凭据与期望凭据不一致。 - nobody + q1w2e3r4:shiro.ini
[users]段无此账号 → 抛UnknownAccountException,异常消息指出 IniRealm 无法找到账号数据。 - alice + q1w2e3r4:凭据正确(对照组)→
LOGIN OK: alice, isAuthenticated = true。 - 三类场景都在
AuthenticationException体系之内,示例按具体子类分别捕获后打印,程序不中断。
八、示例 3:角色与权限校验(AuthzDemo)
8.1 示例源码
创建 /root/shiro-demo/src/main/java/demo/AuthzDemo.java:
package demo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.lang.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
/**
* 示例 3:hasRole / isPermitted / checkPermission 授权校验,alice 与 root 两个账号对照。
*/
public class AuthzDemo {
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityUtils.setSecurityManager(factory.getInstance());
check("alice"); // 角色:user
check("root"); // 角色:admin(通配符授权)
}
private static void check(String username) {
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(username, "q1w2e3r4"));
System.out.println("=== " + username + " ===");
System.out.println("hasRole(user) = " + subject.hasRole("user")
+ ", hasRole(deployer) = " + subject.hasRole("deployer")
+ ", hasRole(admin) = " + subject.hasRole("admin"));
System.out.println("isPermitted(app:read) = " + subject.isPermitted("app:read"));
System.out.println("isPermitted(app:write) = " + subject.isPermitted("app:write"));
System.out.println("isPermitted(app:deploy) = " + subject.isPermitted("app:deploy"));
System.out.println("isPermitted(app:delete) = " + subject.isPermitted("app:delete"));
System.out.println("isPermittedAll(app:read, app:write) = " + subject.isPermittedAll("app:read", "app:write"));
// checkPermission 校验失败时抛 UnauthorizedException
try {
subject.checkPermission("app:delete");
} catch (UnauthorizedException e) {
System.out.println("checkPermission(app:delete) -> UnauthorizedException: " + e.getMessage());
}
subject.logout();
}
}
8.2 运行与回显
cd /root/shiro-demo && mvn exec:java -Dexec.mainClass=demo.AuthzDemo
实测回显(应用段与构建结果):
[demo.AuthzDemo.main()] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager - Enabling session validation scheduler...
=== alice ===
hasRole(user) = true, hasRole(deployer) = false, hasRole(admin) = false
isPermitted(app:read) = true
isPermitted(app:write) = true
isPermitted(app:deploy) = false
isPermitted(app:delete) = false
isPermittedAll(app:read, app:write) = true
checkPermission(app:delete) -> UnauthorizedException: Subject does not have permission [app:delete]
=== root ===
hasRole(user) = false, hasRole(deployer) = false, hasRole(admin) = true
isPermitted(app:read) = true
isPermitted(app:write) = true
isPermitted(app:deploy) = true
isPermitted(app:delete) = true
isPermittedAll(app:read, app:write) = true
[INFO] BUILD SUCCESS
[INFO] Total time: 16.022 s
8.3 结果解读
- alice(user 角色):具备
app:read、app:write;不具备app:deploy、app:delete;isPermittedAll对两项同时判定为 true;checkPermission(app:delete)校验失败抛UnauthorizedException(消息为Subject does not have permission [app:delete])。 - root(admin 角色):
[roles]段中 admin =*(通配符),因此四个权限点全部为 true;hasRole 仅 admin 为 true。 - hasRole 与 isPermitted 的区别:hasRole 判定角色名,isPermitted 判定权限串(支持
资源:操作分层写法);check 系列方法(checkRole/checkPermission)在无权限时抛异常,is/has 系列返回布尔值。
九、注意事项
⚠️ 注意:
三个示例共用一个 SecurityManager 时,每个场景应使用独立 Subject 并在结束后 logout(),避免身份状态串扰(示例均按此约定编写)。
示例进程堆内存建议限制在 256–512MB,通过 MAVEN_OPTS 注入后运行:
export MAVEN_OPTS="-Xms256m -Xmx512m" mvn exec:java -Dexec.mainClass=demo.LoginDemo运行期间另开终端用
jps -v验证 Maven 进程已带堆参数(实测回显):7021 Launcher -Xms256m -Xmx512m -Dclassworlds.conf=/usr/share/maven/bin/m2.conf -Dmaven.home=/usr/share/maven -Dlibrary.jansi.path=/usr/share/maven/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/root/shiro-demo 7076 Jps -Dapplication.home=/usr/lib/jvm/java-17-openjdk-amd64 -Xms8m -Djdk.module.main=jdk.jcmd
sessionId、IniRealm@hashcode等随机值每次运行不同,比对回显时忽略即可。shiro.ini 中的账号与口令 q1w2e3r4 仅为演示凭据,生产环境必须更换并使用加密存储。
十、常见问题
Q1:运行报 NoClassDefFoundError: org/apache/commons/logging/LogFactory?
pom 缺少 jcl-over-slf4j。按第四章补齐依赖后重新 mvn compile,完整排障过程见《Apache Shiro 安装指南》常见问题。
Q2:mvn exec:java 结束后出现 SessionValidationThread 残留 WARNING?
exec:java 方式下 logout() 后会话校验线程不会立即退出导致,程序功能正常;改用 java -cp 直接运行无此现象(见 7.2 方式二)。
Q3:alice 为什么 isPermitted(app:deploy) = false?
shiro.ini [roles] 段中 user 角色仅定义了 app:read, app:write。给 alice 增加 deployer 角色或给 user 角色追加 app:deploy 权限后,重新运行即可变为 true。
Q4:checkPermission 与 isPermitted 该用哪个?
业务上「只判断有无权限再分支」用 isPermitted(返回布尔);「必须具备否则拒绝执行」用 checkPermission(失败抛 UnauthorizedException,语义更强,便于统一异常处理)。
十一、参考链接
- Apache Shiro 官网:
https://shiro.apache.org/ - 10 分钟教程:
https://shiro.apache.org/10-minute-tutorial.html - Java 认证指南:
https://shiro.apache.org/java-authentication-guide.html - Java 授权指南:
https://shiro.apache.org/java-authorization-guide.html - Realm 文档:
https://shiro.apache.org/realm.html