1. 概述
本文将介绍 Apache Shiro —— 一个功能强大的 Java 安全框架。该框架具有高度可定制性和模块化特点,提供认证、授权、加密和会话管理四大核心功能。
Shiro 的设计哲学是简单粗暴:让安全控制变得直观易用,无论是 Web 应用、独立应用还是分布式系统都能轻松集成。
2. 依赖配置
Apache Shiro 提供多个模块,但本教程仅使用核心模块 shiro-core
。在 pom.xml
中添加依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
最新版本可在 Maven Central 获取。
3. 安全管理器配置
SecurityManager
是 Shiro 框架的核心组件,应用中通常只存在一个实例。本教程以桌面环境为例进行演示。
在资源文件夹创建 shiro.ini
配置文件:
[users]
user = password, admin
user2 = password2, editor
user3 = password3, author
[roles]
admin = *
editor = articles:*
author = articles:compose,articles:save
配置说明:
[users]
定义用户凭证:用户名 = 密码, 角色1, 角色2...
[roles]
定义角色权限:admin
拥有所有权限(*
通配符)editor
拥有文章相关全部权限author
仅能撰写和保存文章
通过 IniRealm
加载配置并初始化 SecurityManager
:
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
SecurityManager securityManager = new DefaultSecurityManager(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
现在 SecurityManager
已加载用户凭证和角色定义,接下来进行认证和授权操作。
4. 认证机制
在 Shiro 中,Subject
表示与系统交互的任何实体(用户、脚本或 REST 客户端)。通过 SecurityUtils.getSubject()
获取当前主体 currentUser
。
认证流程实现:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token
= new UsernamePasswordToken("user", "password");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.error("用户名不存在!", uae);
} catch (IncorrectCredentialsException ice) {
log.error("密码错误!", ice);
} catch (LockedAccountException lae) {
log.error("账户已锁定!", lae);
} catch (AuthenticationException ae) {
log.error("认证异常!", ae);
}
}
关键步骤:
- 检查当前主体是否未认证
- 创建包含用户名和密码的令牌
- 调用
login()
进行认证 - 捕获特定异常处理不同错误场景
⚠️ 注意:可通过继承 AccountException
创建自定义异常。
5. 授权机制
认证验证用户身份,授权控制资源访问权限。回顾 shiro.ini
中的角色定义:
角色 | 权限范围 |
---|---|
admin | 所有权限 (* ) |
editor | 文章相关全部权限 (articles:* ) |
author | 仅撰写和保存文章 |
根据角色显示欢迎信息:
if (currentUser.hasRole("admin")) {
log.info("欢迎管理员");
} else if(currentUser.hasRole("editor")) {
log.info("欢迎编辑!");
} else if(currentUser.hasRole("author")) {
log.info("欢迎作者");
} else {
log.info("欢迎访客");
}
检查具体操作权限:
if(currentUser.isPermitted("articles:compose")) {
log.info("可撰写文章");
} else {
log.info("无撰写权限!");
}
if(currentUser.isPermitted("articles:save")) {
log.info("可保存文章");
} else {
log.info("无保存权限");
}
if(currentUser.isPermitted("articles:publish")) {
log.info("可发布文章");
} else {
log.info("无发布权限");
}
6. 领域配置
实际应用中,用户凭证应从数据库而非 shiro.ini
获取。这时需要自定义 Realm
—— Shiro 中用于访问安全数据的 DAO。
创建自定义领域继承 JdbcRealm
:
public class MyCustomRealm extends JdbcRealm {
// 模拟数据库存储
private Map<String, String> credentials = new HashMap<>();
private Map<String, Set<String>> roles = new HashMap<>();
private Map<String, Set<String>> perm = new HashMap<>();
{
credentials.put("user", "password");
credentials.put("user2", "password2");
credentials.put("user3", "password3");
roles.put("user", new HashSet<>(Arrays.asList("admin")));
roles.put("user2", new HashSet<>(Arrays.asList("editor")));
roles.put("user3", new HashSet<>(Arrays.asList("author")));
perm.put("admin", new HashSet<>(Arrays.asList("*")));
perm.put("editor", new HashSet<>(Arrays.asList("articles:*")));
perm.put("author",
new HashSet<>(Arrays.asList("articles:compose",
"articles:save")));
}
}
重写认证方法:
protected AuthenticationInfo
doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
UsernamePasswordToken uToken = (UsernamePasswordToken) token;
if(uToken.getUsername() == null
|| uToken.getUsername().isEmpty()
|| !credentials.containsKey(uToken.getUsername())) {
throw new UnknownAccountException("用户名不存在!");
}
return new SimpleAuthenticationInfo(
uToken.getUsername(),
credentials.get(uToken.getUsername()),
getName());
}
✅ 密码加盐处理(推荐):
return new SimpleAuthenticationInfo(
uToken.getUsername(),
credentials.get(uToken.getUsername()),
ByteSource.Util.bytes("salt"),
getName()
);
最后替换默认领域:
Realm realm = new MyCustomRealm();
SecurityManager securityManager = new DefaultSecurityManager(realm);
默认使用 SimpleCredentialsMatcher
进行凭证匹配。若使用哈希密码,需配置 HashedCredentialsMatcher
(详见官方文档)。
7. 登出操作
登出只需调用单个方法,使会话失效并清除用户状态:
currentUser.logout();
8. 会话管理
Shiro 提供企业级会话管理:
- Web 环境:默认使用
HttpSession
- 独立应用:使用内置会话系统
会话操作示例:
Session session = currentUser.getSession();
session.setAttribute("key", "value");
String value = (String) session.getAttribute("key");
if (value.equals("value")) {
log.info("获取到正确值! [" + value + "]");
}
9. Spring Web 应用集成
将 Shiro 集成到 Spring Boot 应用(重点在 Shiro 配置,非 Spring 实现)。
9.1 依赖配置
添加 Spring Boot 父依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
</parent>
添加核心依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>${apache-shiro-core-version}</version>
</dependency>
9.2 配置类
shiro-spring-boot-web-starter
自动配置基础功能,但仍需自定义领域和过滤器链:
@Bean
public Realm realm() {
return new MyCustomRealm();
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition filter
= new DefaultShiroFilterChainDefinition();
filter.addPathDefinition("/secure", "authc"); // 需认证
filter.addPathDefinition("/**", "anon"); // 允许匿名访问
return filter;
}
默认过滤器说明:
authc
:表单认证anon
:匿名访问
更多默认过滤器见官方文档。
在 application.properties
覆盖默认配置:
shiro.loginUrl = /login
shiro.successUrl = /secure
shiro.unauthorizedUrl = /login
9.3 认证与授权实现
创建控制器处理登录逻辑:
Subject subject = SecurityUtils.getSubject();
if(!subject.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(
cred.getUsername(), cred.getPassword(), cred.isRememberMe());
try {
subject.login(token);
} catch (AuthenticationException ae) {
ae.printStackTrace();
attr.addFlashAttribute("error", "凭证无效");
return "redirect:/login";
}
}
return "redirect:/secure";
安全页面处理授权逻辑:
Subject currentUser = SecurityUtils.getSubject();
String role = "", permission = "";
if(currentUser.hasRole("admin")) {
role = "管理员权限";
} else if(currentUser.hasRole("editor")) {
role = "编辑权限";
} else if(currentUser.hasRole("author")) {
role = "作者权限";
}
if(currentUser.isPermitted("articles:compose")) {
permission += "可撰写文章, ";
} else {
permission += "无撰写权限!, ";
}
if(currentUser.isPermitted("articles:save")) {
permission += "可保存文章, ";
} else {
permission += "无保存权限, ";
}
if(currentUser.isPermitted("articles:publish")) {
permission += "可发布文章";
} else {
permission += "无发布权限";
}
modelMap.addAttribute("username", currentUser.getPrincipal());
modelMap.addAttribute("permission", permission);
modelMap.addAttribute("role", role);
return "secure";
✅ 完成!Shiro 已成功集成到 Spring Boot 应用。可结合注解实现更细粒度的安全控制。
10. JEE 集成
在 JEE 应用中集成 Shiro 只需配置 web.xml
。默认读取类路径下的 shiro.ini
文件。详细配置示例见官方文档,JSP 标签库见此处。
11. 总结
本文深入探讨了 Apache Shiro 的认证授权机制,重点介绍了:
- ✅ 核心组件
SecurityManager
的配置 - ✅ 自定义
Realm
实现数据源集成 - ✅ 会话管理与企业级应用集成
- ✅ Spring Boot 环境下的最佳实践
完整源代码可在 GitHub 获取。踩坑提示:实际项目中务必使用哈希密码加盐存储,避免明文密码风险!