Apache Shiro 全面指南:从入门到高级应用

一、Shiro 概述与核心架构

1.1 什么是 Shiro?

Apache Shiro 是一个强大且易用的 Java 安全框架,它提供了认证(Authentication)、授权(Authorization)、加密(Cryptography)和会话管理(Session Management)等功能。与 Spring Security 相比,Shiro 的设计更加直观和简单,同时又不失灵活性。

Shiro 的核心优势:

简单性:API 设计友好,学习曲线平缓全面性:覆盖了应用安全的各个方面灵活性:可以轻松集成到任何应用环境中可扩展性:所有组件都支持自定义扩展跨平台:不仅限于 Web 应用,也可用于非 Web 环境

1.2 Shiro 核心架构

Shiro 的架构遵循了"分而治之"的设计理念,将安全功能划分为多个独立的组件:

+---------------------------------------------------+

| Application |

+---------------------------------------------------+

| Shiro |

+-------------------+----------------+--------------+

| Authentication | Authorization | Session Mgmt |

+-------------------+----------------+--------------+

| Cryptography | Cache Mgmt | Concurrency |

+-------------------+----------------+--------------+

| Realms |

+---------------------------------------------------+

| Security Manager |

+---------------------------------------------------+

核心组件解析:

Subject:当前操作用户的安全特定"视图"SecurityManager:Shiro 的核心,协调各安全组件Authenticator:负责认证(登录)操作Authorizer:负责授权(访问控制)决策SessionManager:管理用户会话CacheManager:提供缓存支持以提高性能Cryptography:提供加密/解密功能Realms:连接安全数据和 Shiro 的桥梁

二、快速入门:第一个 Shiro 应用

2.1 环境准备

2.1.1 添加 Maven 依赖

org.apache.shiro

shiro-core

1.9.0

org.apache.shiro

shiro-web

1.9.0

org.apache.shiro

shiro-spring

1.9.0

2.1.2 基本配置

创建 shiro.ini 配置文件:

[users]

# 格式:username = password, role1, role2, ...

admin = secret, admin

user1 = password, user

user2 = 123456, user

[roles]

# 角色权限定义

admin = *

user = user:read,user:write

2.2 第一个 Shiro 示例

public class Quickstart {

public static void main(String[] args) {

// 1. 创建SecurityManager工厂

Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");

// 2. 获取SecurityManager实例

SecurityManager securityManager = factory.getInstance();

// 3. 绑定SecurityManager到运行环境

SecurityUtils.setSecurityManager(securityManager);

// 4. 获取当前用户(Subject)

Subject currentUser = SecurityUtils.getSubject();

// 5. 模拟登录

if (!currentUser.isAuthenticated()) {

UsernamePasswordToken token = new UsernamePasswordToken("admin", "secret");

try {

currentUser.login(token);

System.out.println("登录成功!");

} catch (AuthenticationException ae) {

System.out.println("登录失败: " + ae.getMessage());

}

}

// 6. 权限检查

if (currentUser.hasRole("admin")) {

System.out.println("您有admin角色");

} else {

System.out.println("您没有admin角色");

}

// 7. 权限检查

if (currentUser.isPermitted("user:create")) {

System.out.println("您有创建用户的权限");

} else {

System.out.println("您没有创建用户的权限");

}

// 8. 登出

currentUser.logout();

}

}

三、Shiro 核心概念深入

3.1 Subject 详解

Subject 是 Shiro 的核心概念,代表当前与应用交互的实体(用户、第三方服务等)。

关键方法:

方法描述getPrincipal()获取用户身份(如用户名)getSession()获取关联的Sessionlogin()/logout()登录/登出isAuthenticated()是否已认证hasRole()检查角色isPermitted()检查权限

多线程环境中的Subject:

// 在另一个线程中获取Subject

Runnable runnable = () -> {

Subject subject = SecurityUtils.getSubject();

// 执行操作

};

new Thread(runnable).start();

3.2 SecurityManager 解析

SecurityManager 是 Shiro 架构的核心,负责协调各安全组件。

常见实现:

DefaultSecurityManager:默认实现DefaultWebSecurityManager:Web应用专用

配置示例:

// 创建Realm

Realm realm = new IniRealm("classpath:shiro.ini");

// 创建SecurityManager

SecurityManager securityManager = new DefaultSecurityManager(realm);

// 配置SecurityManager

SecurityUtils.setSecurityManager(securityManager);

3.3 Realm 深入

Realm 是 Shiro 与应用安全数据的桥梁,负责获取认证和授权信息。

内置Realm实现:

IniRealm:从INI文件读取用户信息JdbcRealm:从数据库读取用户信息TextConfigurationRealm:内存配置LdapRealm:连接LDAP服务器ActiveDirectoryRealm:连接Active Directory

自定义Realm示例:

public class MyRealm extends AuthorizingRealm {

// 授权

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

String username = (String) principals.getPrimaryPrincipal();

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

// 添加角色

info.addRoles(getRolesFromDB(username));

// 添加权限

info.addStringPermissions(getPermissionsFromDB(username));

return info;

}

// 认证

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)

throws AuthenticationException {

UsernamePasswordToken upToken = (UsernamePasswordToken) token;

String username = upToken.getUsername();

// 从数据库获取用户信息

User user = getUserFromDB(username);

if (user == null) {

throw new UnknownAccountException("用户不存在");

}

// 返回认证信息

return new SimpleAuthenticationInfo(

user.getUsername(), // 身份

user.getPassword(), // 凭证

getName() // realm名称

);

}

// 模拟从数据库获取角色

private Set getRolesFromDB(String username) {

// 实际应用中应从数据库查询

Set roles = new HashSet<>();

if ("admin".equals(username)) {

roles.add("admin");

roles.add("user");

} else {

roles.add("user");

}

return roles;

}

// 模拟从数据库获取权限

private Set getPermissionsFromDB(String username) {

Set permissions = new HashSet<>();

if ("admin".equals(username)) {

permissions.add("user:create");

permissions.add("user:update");

permissions.add("user:delete");

permissions.add("user:view");

} else {

permissions.add("user:view");

}

return permissions;

}

// 模拟从数据库获取用户

private User getUserFromDB(String username) {

if ("admin".equals(username)) {

return new User("admin", "secret");

} else if ("user".equals(username)) {

return new User("user", "password");

}

return null;

}

// 简单的User类

private static class User {

private String username;

private String password;

public User(String username, String password) {

this.username = username;

this.password = password;

}

public String getUsername() { return username; }

public String getPassword() { return password; }

}

}

四、Shiro 认证机制

4.1 认证流程详解

Shiro 的认证流程可以分为以下几个步骤:

收集用户身份/凭证:通常是用户名/密码提交认证:调用 Subject.login() 方法Realm 认证:SecurityManager 委托 Realm 进行认证成功/失败处理:返回认证结果

认证流程图:

+---------------+ +----------------+ +-------------------+

| Subject | --> | SecurityManager| --> | Authenticator |

+---------------+ +----------------+ +-------------------+

|

v

+-------------------+

| Realm(s) |

+-------------------+

4.2 多种认证方式

4.2.1 用户名/密码认证

UsernamePasswordToken token = new UsernamePasswordToken(username, password);

token.setRememberMe(true); // 记住我功能

try {

currentUser.login(token);

// 认证成功

} catch (UnknownAccountException uae) {

// 用户名不存在

} catch (IncorrectCredentialsException ice) {

// 密码错误

} catch (LockedAccountException lae) {

// 账户锁定

} catch (AuthenticationException ae) {

// 其他认证错误

}

4.2.2 多Realm认证

配置多个Realm:

[main]

# 定义多个Realm

realm1 = com.example.MyRealm1

realm2 = com.example.MyRealm2

# 配置认证策略

authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy

# 配置SecurityManager

securityManager.realms = $realm1, $realm2

securityManager.authenticator.authenticationStrategy = $authcStrategy

认证策略:

FirstSuccessfulStrategy:第一个成功认证的Realm即返回AtLeastOneSuccessfulStrategy:至少一个Realm认证成功AllSuccessfulStrategy:所有Realm都必须认证成功

4.3 记住我功能

Shiro 提供了开箱即用的"记住我"功能:

token.setRememberMe(true);

配置RememberMe:

[main]

# Cookie配置

rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager

rememberMeManager.cookie.name = rememberMe

rememberMeManager.cookie.maxAge = 2592000 # 30天

securityManager.rememberMeManager = $rememberMeManager

五、Shiro 授权机制

5.1 授权基础

Shiro 支持三种授权方式:

编程式:在代码中检查权限

if (subject.hasRole("admin")) {

// 有admin角色

}

if (subject.isPermitted("user:create")) {

// 有创建用户的权限

}

注解式:使用Java注解

@RequiresRoles("admin")

public void deleteUser() {

// 需要admin角色才能执行

}

@RequiresPermissions("user:create")

public void createUser() {

// 需要user:create权限才能执行

}

标签式(JSP/GSP):

5.2 权限字符串详解

Shiro 的权限字符串支持通配符和多种格式:

简单格式:printer:query

第一部分:资源类型(如printer)第二部分:操作(如query) 多级格式:printer:query:lp7200

第三部分:资源实例ID 通配符:

printer:*:对printer的所有操作printer:query:*:查询所有printer实例*:query:对所有资源的查询操作

5.3 自定义授权

5.3.1 自定义Permission

public class MyPermission implements Permission {

private String resource;

private String action;

private String instance;

public MyPermission(String permissionString) {

String[] parts = permissionString.split(":");

this.resource = parts.length > 0 ? parts[0] : null;

this.action = parts.length > 1 ? parts[1] : null;

this.instance = parts.length > 2 ? parts[2] : null;

}

@Override

public boolean implies(Permission p) {

if (!(p instanceof MyPermission)) {

return false;

}

MyPermission mp = (MyPermission) p;

// 检查资源是否匹配

if (resource != null && !resource.equals(mp.resource)) {

return false;

}

// 检查操作是否匹配

if (action != null && !action.equals(mp.action)) {

return false;

}

// 检查实例是否匹配

if (instance != null && !instance.equals(mp.instance)) {

return false;

}

return true;

}

}

5.3.2 自定义RolePermissionResolver

public class MyRolePermissionResolver implements RolePermissionResolver {

@Override

public Collection resolvePermissionsInRole(String roleString) {

Set permissions = new HashSet<>();

if ("admin".equals(roleString)) {

permissions.add(new WildcardPermission("user:*"));

permissions.add(new WildcardPermission("system:*"));

} else if ("user".equals(roleString)) {

permissions.add(new WildcardPermission("user:read,update"));

}

return permissions;

}

}

六、Shiro 与 Web 集成

6.1 Shiro Filter 详解

Shiro 为 Web 应用提供了一系列 Filter:

Filter Name描述anon匿名访问,不需要认证authc需要认证才能访问authcBasic基本HTTP认证logout退出登录noSession不创建会话perms需要特定权限port需要特定端口restREST风格权限检查roles需要特定角色ssl需要HTTPSuser需要已认证或记住我

配置示例(web.xml):

ShiroFilter

org.apache.shiro.web.servlet.ShiroFilter

ShiroFilter

/*

配置示例(shiro.ini):

[urls]

/login = authc

/logout = logout

/static/** = anon

/admin/** = authc, roles[admin]

/user/** = authc, perms["user:read"]

/** = user

6.2 会话管理

Shiro 提供了强大的会话管理功能,可以独立于容器:

Subject currentUser = SecurityUtils.getSubject();

Session session = currentUser.getSession();

session.setAttribute("key", "value");

String value = (String) session.getAttribute("key");

会话配置:

[main]

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager

# 会话超时时间(毫秒)

sessionManager.globalSessionTimeout = 1800000 # 30分钟

# 会话验证间隔

sessionManager.sessionValidationInterval = 1800000 # 30分钟

securityManager.sessionManager = $sessionManager

6.3 记住我与SSO

记住我配置:

[main]

rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager

rememberMeManager.cipherKey = base64:abc123...= # 加密密钥

securityManager.rememberMeManager = $rememberMeManager

SSO集成(以CAS为例):

[main]

casRealm = org.apache.shiro.cas.CasRealm

casRealm.casServerUrlPrefix = https://cas.example.org/cas

casRealm.applicationUrl = https://myapp.example.org

securityManager.realms = $casRealm

七、Shiro 高级主题

7.1 缓存集成

Shiro 支持多种缓存实现(EhCache、Redis等):

[main]

cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager

cacheManager.cacheManagerConfigFile = classpath:ehcache.xml

securityManager.cacheManager = $cacheManager

7.2 加密与哈希

Shiro 提供了强大的加密工具:

// 使用MD5哈希

String hashed = new Md5Hash("password", "salt", 2).toHex();

// 使用SHA-256哈希

String hashed = new Sha256Hash("password", "salt", 1024).toBase64();

// 使用BCrypt

String hashed = new BCryptPasswordEncoder().encode("password");

配置密码服务:

[main]

credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher

credentialsMatcher.hashIterations = 1024

credentialsMatcher.storedCredentialsHexEncoded = true

myRealm = com.example.MyRealm

myRealm.credentialsMatcher = $credentialsMatcher

securityManager.realms = $myRealm

7.3 并发控制

Shiro 可以控制并发登录:

[main]

sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager

sessionManager.sessionDAO = $sessionDAO

# 配置并发控制

concurrencyFilter = org.apache.shiro.web.filter.session.ConcurrentSessionFilter

concurrencyFilter.sessionManager = $sessionManager

concurrencyFilter.kickoutUrl = /login?kickout=1

securityManager.sessionManager = $sessionManager

八、Shiro 与 Spring/Spring Boot 集成

8.1 与 Spring 集成

配置示例:

/static/** = anon

/login = authc

/logout = logout

/** = user

class="org.apache.shiro.authc.credential.Sha256CredentialsMatcher">

class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

8.2 与 Spring Boot 集成

依赖配置:

org.apache.shiro

shiro-spring-boot-web-starter

1.9.0

配置类:

@Configuration

public class ShiroConfig {

@Bean

public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {

ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

factoryBean.setSecurityManager(securityManager);

Map filterChainDefinitionMap = new LinkedHashMap<>();

filterChainDefinitionMap.put("/static/**", "anon");

filterChainDefinitionMap.put("/login", "authc");

filterChainDefinitionMap.put("/logout", "logout");

filterChainDefinitionMap.put("/**", "user");

factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

factoryBean.setLoginUrl("/login");

factoryBean.setSuccessUrl("/home");

factoryBean.setUnauthorizedUrl("/unauthorized");

return factoryBean;

}

@Bean

public SecurityManager securityManager(Realm realm) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

securityManager.setRealm(realm);

return securityManager;

}

@Bean

public Realm realm() {

MyRealm realm = new MyRealm();

realm.setCredentialsMatcher(credentialsMatcher());

return realm;

}

@Bean

public CredentialsMatcher credentialsMatcher() {

return new Sha256CredentialsMatcher();

}

@Bean

public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {

return new LifecycleBeanPostProcessor();

}

}

九、Shiro 最佳实践

9.1 安全最佳实践

密码安全:

始终使用加盐哈希使用强哈希算法(如SHA-256、BCrypt)避免使用弱密码 会话安全:

使用HTTPS保护会话设置合理的会话超时防止会话固定攻击 权限设计:

遵循最小权限原则使用RBAC(基于角色的访问控制)模型定期审查权限分配

9.2 性能优化

缓存策略:

缓存认证和授权信息使用分布式缓存(如Redis)用于集群环境 会话管理:

对于无状态API,考虑禁用会话优化会话持久化策略 Realm优化:

批量获取权限信息避免重复查询数据库

9.3 常见问题解决

ClassCastException:

确保Subject绑定到正确的线程检查类加载器问题 权限不生效:

检查权限字符串格式确认Realm正确配置检查缓存是否过期 会话丢失:

检查集群配置确认会话持久化配置正确

十、总结

Apache Shiro 作为一个功能全面而又简单易用的安全框架,为Java应用提供了强大的安全保障。通过本文的学习,我们了解了:

Shiro 的核心概念和架构设计认证和授权的实现原理Web集成和会话管理与Spring/Spring Boot的集成高级特性和最佳实践

无论是简单的Web应用还是复杂的企业级系统,Shiro都能提供合适的安全解决方案。希望本文能成为你Shiro学习之旅的有力参考,帮助你在实际项目中构建更加安全可靠的系统。

PS:如果你在学习过程中遇到问题,别担心!欢迎在评论区留言,我会尽力帮你解决!😄

热门