Shiro入门教程 - Apache Shiro安全框架完整学习指南
Shiro入门教程 - Apache Shiro安全框架完整学习指南
目录
- Shiro简介
- 环境搭建
- Shiro核心概念
- 第一个Shiro程序
- 认证(Authentication)
- 授权(Authorization)
- 自定义Realm
- 加密和密码管理
- 会话管理
- Shiro与Spring集成
- Shiro与Spring Boot集成
- Web应用集成
- 缓存配置
- 实际项目案例
- 最佳实践与常见问题
- 总结与进阶
1. Shiro简介
Apache Shiro是一个功能强大且易于使用的Java安全框架,提供了认证(Authentication)、授权(Authorization)、加密(Cryptography)和会话管理(Session Management)等功能。
核心特点:
- ✅ 简单易用:API设计简洁,学习曲线平缓
- ✅ 功能全面:认证、授权、加密、会话管理一应俱全
- ✅ 灵活性强:可以轻松集成到任何应用
- ✅ 性能优秀:轻量级框架,性能开销小
- ✅ 广泛支持:支持Web应用、命令行应用、移动应用等
- 认证(Authentication):验证用户身份
- 授权(Authorization):控制用户访问权限
- 会话管理(Session Management):管理用户会话
- 加密(Cryptography):保护数据安全
- Web支持:提供Web应用安全支持
- 缓存:提高性能
- 并发:支持多线程环境
- 测试:提供测试支持
| 特性 | Shiro | Spring Security |
|---|---|---|
| 学习曲线 | 平缓 | 陡峭 |
| 配置复杂度 | 简单 | 复杂 |
| 功能完整性 | 完整 | 更完整 |
| 灵活性 | 高 | 中 |
| 适用场景 | 中小型项目 | 大型企业项目 |
- Web应用安全
- RESTful API安全
- 移动应用后端
- 企业应用权限管理
- 单点登录(SSO)系统
2. 环境搭建
- JDK 8或更高版本
- Maven 3.6+ 或 Gradle
- IDE(IntelliJ IDEA、Eclipse等)
使用IDE创建
- 打开IntelliJ IDEA
- File → New → Project
- 选择Maven,点击Next
- 填写GroupId和ArtifactId
- 点击Finish
使用命令行创建
mvn archetype:generate -DgroupId=com.example -DartifactId=shiro-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false在pom.xml文件中添加Shiro依赖:
<?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>com.example</groupId>
<artifactId>shiro-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<shiro.version>1.13.0</shiro.version>
<slf4j.version>1.7.36</slf4j.version>
<logback.version>1.2.12</logback.version>
<junit.version>4.13.2</junit.version>
</properties>
<dependencies>
<!-- Shiro核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- Shiro Web支持(Web应用需要) -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- Shiro Spring支持(Spring集成需要) -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- 日志框架 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- JUnit测试框架 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>2.4 项目目录结构
创建标准的Maven项目结构:
shiro-demo/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── realm/
│ │ │ │ └── CustomRealm.java
│ │ │ ├── util/
│ │ │ │ └── ShiroUtil.java
│ │ │ └── Main.java
│ │ └── resources/
│ │ └── shiro.ini
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── ShiroTest.java
└── pom.xml3. Shiro核心概念
Shiro的核心架构包含三个主要组件:
Subject(主体)
Subject是当前执行用户的特定视图,可以是一个人、第三方服务、守护进程账户等。
Subject currentUser = SecurityUtils.getSubject();SecurityManager(安全管理器)
SecurityManager是Shiro的核心,管理所有Subject的安全操作。
Realm(域)
Realm是Shiro与应用程序安全数据之间的桥梁,用于获取安全数据(用户、角色、权限等)。
Subject → SecurityManager → Realm → 数据源(数据库、文件等)应用程序调用Subject.login()
Subject委托给SecurityManager
SecurityManager委托给Authenticator
Authenticator使用Realm获取用户信息
验证用户凭证
返回认证结果
应用程序调用Subject的权限检查方法
Subject委托给SecurityManager
SecurityManager委托给Authorizer
Authorizer使用Realm获取权限信息
检查用户权限
返回授权结果
4. 第一个Shiro程序
创建src/main/resources/shiro.ini:
[users]
# 格式:username=password,role1,role2
zhangsan=123456,admin
lisi=123456,user
wangwu=123456,user
[roles]
# 格式:rolename=permission1,permission2
admin=user:create,user:update,user:delete,user:view
user=user:view创建Main.java:
package com.example;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class Main {
public static void main(String[] args) {
// 1. 加载Shiro配置
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 2. 获取当前Subject
Subject currentUser = SecurityUtils.getSubject();
// 3. 测试用户是否已认证
if (!currentUser.isAuthenticated()) {
// 创建用户名/密码令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
token.setRememberMe(true); // 记住我
try {
// 登录
currentUser.login(token);
System.out.println("登录成功!");
} catch (UnknownAccountException uae) {
System.out.println("用户名不存在:" + uae.getMessage());
} catch (IncorrectCredentialsException ice) {
System.out.println("密码错误:" + ice.getMessage());
} catch (LockedAccountException lae) {
System.out.println("账户被锁定:" + lae.getMessage());
} catch (AuthenticationException ae) {
System.out.println("认证失败:" + ae.getMessage());
}
}
// 4. 显示用户信息
System.out.println("用户:" + currentUser.getPrincipal());
System.out.println("是否已认证:" + currentUser.isAuthenticated());
System.out.println("是否有admin角色:" + currentUser.hasRole("admin"));
System.out.println("是否有user:create权限:" + currentUser.isPermitted("user:create"));
// 5. 退出
currentUser.logout();
System.out.println("已退出登录");
}
}运行Main类的main方法,你应该能看到:
- 登录成功的消息
- 用户信息
- 角色和权限检查结果
5. 认证(Authentication)
认证是验证用户身份的过程,即"你是谁"。
// 1. 获取Subject
Subject currentUser = SecurityUtils.getSubject();
// 2. 创建认证令牌
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
// 3. 执行登录
currentUser.login(token);
// 4. 检查认证结果
if (currentUser.isAuthenticated()) {
// 认证成功
}Shiro支持多种认证令牌:
UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
token.setRememberMe(true); // 记住我
currentUser.login(token);自定义Token
public class CustomToken implements AuthenticationToken {
private String username;
private String password;
private String captcha;
public CustomToken(String username, String password, String captcha) {
this.username = username;
this.password = password;
this.captcha = captcha;
}
@Override
public Object getPrincipal() {
return username;
}
@Override
public Object getCredentials() {
return password;
}
public String getCaptcha() {
return captcha;
}
}try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
// 用户名不存在
System.out.println("用户名不存在");
} catch (IncorrectCredentialsException ice) {
// 密码错误
System.out.println("密码错误");
} catch (LockedAccountException lae) {
// 账户被锁定
System.out.println("账户被锁定");
} catch (ExcessiveAttemptsException eae) {
// 尝试次数过多
System.out.println("尝试次数过多");
} catch (AuthenticationException ae) {
// 其他认证异常
System.out.println("认证失败:" + ae.getMessage());
}UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
token.setRememberMe(true); // 启用记住我
currentUser.login(token);
// 检查是否通过记住我登录
if (currentUser.isRemembered()) {
System.out.println("通过记住我登录");
}Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();6. 授权(Authorization)
授权是控制用户访问权限的过程,即"你能做什么"。
检查角色
Subject currentUser = SecurityUtils.getSubject();
// 检查是否有单个角色
if (currentUser.hasRole("admin")) {
System.out.println("有admin角色");
}
// 检查是否有多个角色(全部需要)
boolean[] hasRoles = currentUser.hasRoles(Arrays.asList("admin", "user"));
if (hasRoles[0] && hasRoles[1]) {
System.out.println("同时有admin和user角色");
}
// 检查是否有任一角色
boolean hasAnyRole = currentUser.hasAnyRole(Arrays.asList("admin", "user"));断言角色
// 如果没有角色会抛出异常
currentUser.checkRole("admin");
currentUser.checkRoles("admin", "user");检查权限
Subject currentUser = SecurityUtils.getSubject();
// 检查单个权限
if (currentUser.isPermitted("user:create")) {
System.out.println("有创建用户权限");
}
// 检查多个权限(全部需要)
boolean[] permitted = currentUser.isPermitted("user:create", "user:update");
if (permitted[0] && permitted[1]) {
System.out.println("有创建和更新权限");
}
// 检查是否有任一权限
boolean permittedAny = currentUser.isPermittedAny("user:create", "user:update");断言权限
// 如果没有权限会抛出异常
currentUser.checkPermission("user:create");
currentUser.checkPermissions("user:create", "user:update");Shiro支持多种权限字符串格式:
单个权限
user:create
user:update
user:delete通配符权限
user:* // 所有用户权限
user:create:* // 所有创建相关的用户权限
*:view // 所有查看权限实例级权限
user:delete:1 // 删除ID为1的用户
printer:print:laserjet4400n // 使用特定打印机打印public class UserService {
public void createUser(User user) {
Subject currentUser = SecurityUtils.getSubject();
// 检查权限,没有权限会抛出异常
currentUser.checkPermission("user:create");
// 执行创建用户逻辑
// ...
}
public void deleteUser(Long userId) {
Subject currentUser = SecurityUtils.getSubject();
// 检查实例级权限
currentUser.checkPermission("user:delete:" + userId);
// 执行删除用户逻辑
// ...
}
}7. 自定义Realm
默认的IniRealm只能从配置文件中读取用户信息,实际项目中通常需要从数据库读取。
创建CustomRealm.java:
package com.example.realm;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashSet;
import java.util.Set;
public class CustomRealm extends AuthorizingRealm {
/**
* 授权方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取用户名
String username = (String) principals.getPrimaryPrincipal();
// 从数据库查询用户角色和权限
Set<String> roles = getRolesByUsername(username);
Set<String> permissions = getPermissionsByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
/**
* 认证方法
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获取用户名
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 从数据库查询用户信息
String password = getPasswordByUsername(username);
if (password == null) {
throw new UnknownAccountException("用户不存在");
}
// 创建认证信息
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
username, // principal(身份)
password, // credentials(凭证)
getName() // realm name
);
return authenticationInfo;
}
/**
* 模拟从数据库获取密码
*/
private String getPasswordByUsername(String username) {
// 实际应该从数据库查询
if ("zhangsan".equals(username)) {
return "123456";
}
return null;
}
/**
* 模拟从数据库获取角色
*/
private Set<String> getRolesByUsername(String username) {
Set<String> roles = new HashSet<>();
if ("zhangsan".equals(username)) {
roles.add("admin");
roles.add("user");
} else if ("lisi".equals(username)) {
roles.add("user");
}
return roles;
}
/**
* 模拟从数据库获取权限
*/
private Set<String> getPermissionsByUsername(String username) {
Set<String> permissions = new HashSet<>();
if ("zhangsan".equals(username)) {
permissions.add("user:create");
permissions.add("user:update");
permissions.add("user:delete");
permissions.add("user:view");
} else if ("lisi".equals(username)) {
permissions.add("user:view");
}
return permissions;
}
}更新shiro.ini:
[main]
# 配置自定义Realm
customRealm=com.example.realm.CustomRealm
securityManager.realms=$customRealmFactory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
currentUser.login(token);
// 现在会使用自定义Realm进行认证和授权8. 加密和密码管理
存储明文密码是不安全的,应该存储加密后的密码。
MD5加密
import org.apache.shiro.crypto.hash.Md5Hash;
String password = "123456";
String salt = "salt"; // 盐值
Md5Hash md5Hash = new Md5Hash(password, salt);
String encryptedPassword = md5Hash.toHex();
System.out.println("MD5加密后:" + encryptedPassword);SHA加密
import org.apache.shiro.crypto.hash.Sha256Hash;
String password = "123456";
String salt = "salt";
Sha256Hash sha256Hash = new Sha256Hash(password, salt);
String encryptedPassword = sha256Hash.toHex();
System.out.println("SHA256加密后:" + encryptedPassword);通用加密工具
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
String password = "123456";
String salt = "salt";
String algorithmName = "MD5"; // 或 "SHA-256", "SHA-512" 等
int hashIterations = 1024; // 迭代次数
SimpleHash hash = new SimpleHash(algorithmName, password, salt, hashIterations);
String encryptedPassword = hash.toHex();
System.out.println("加密后:" + encryptedPassword);public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 从数据库获取加密后的密码和盐值
String encryptedPassword = getEncryptedPasswordByUsername(username);
String salt = getSaltByUsername(username);
if (encryptedPassword == null) {
throw new UnknownAccountException("用户不存在");
}
// 创建认证信息,Shiro会自动验证密码
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
username,
encryptedPassword, // 加密后的密码
ByteSource.Util.bytes(salt), // 盐值
getName()
);
return authenticationInfo;
}
}package com.example.util;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
public class PasswordUtil {
private static final String ALGORITHM_NAME = "MD5";
private static final int HASH_ITERATIONS = 1024;
/**
* 加密密码
*/
public static String encryptPassword(String password, String salt) {
SimpleHash hash = new SimpleHash(
ALGORITHM_NAME,
password,
ByteSource.Util.bytes(salt),
HASH_ITERATIONS
);
return hash.toHex();
}
/**
* 生成随机盐值
*/
public static String generateSalt() {
return java.util.UUID.randomUUID().toString().replace("-", "");
}
/**
* 验证密码
*/
public static boolean verifyPassword(String password, String salt, String encryptedPassword) {
String newEncryptedPassword = encryptPassword(password, salt);
return newEncryptedPassword.equals(encryptedPassword);
}
}9. 会话管理
Shiro提供了完整的会话管理功能,可以在任何环境中使用。
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
// 设置会话属性
session.setAttribute("key", "value");
// 获取会话属性
String value = (String) session.getAttribute("key");
// 获取会话ID
String sessionId = session.getId().toString();
// 获取会话超时时间(毫秒)
long timeout = session.getTimeout();import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
public class CustomSessionListener implements SessionListener {
@Override
public void onStart(Session session) {
System.out.println("会话创建:" + session.getId());
}
@Override
public void onStop(Session session) {
System.out.println("会话停止:" + session.getId());
}
@Override
public void onExpiration(Session session) {
System.out.println("会话过期:" + session.getId());
}
}在shiro.ini中配置:
[main]
# 配置会话管理器
sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionManager.globalSessionTimeout=1800000
sessionManager.sessionListeners=$customSessionListener
customSessionListener=com.example.CustomSessionListener10. Shiro与Spring集成
<dependencies>
<!-- Spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<!-- Shiro Spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.13.0</version>
</dependency>
</dependencies>创建applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 自定义Realm -->
<bean id="customRealm" class="com.example.realm.CustomRealm"/>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<property name="realm" ref="customRealm"/>
</bean>
<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 启用Shiro注解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresPermissions;
@Service
public class UserService {
@RequiresAuthentication
public void authenticatedMethod() {
// 需要认证才能访问
}
@RequiresRoles("admin")
public void adminMethod() {
// 需要admin角色才能访问
}
@RequiresPermissions("user:create")
public void createUser() {
// 需要user:create权限才能访问
}
@RequiresRoles({"admin", "user"})
@RequiresPermissions("user:view")
public void viewUser() {
// 需要admin或user角色,并且有user:view权限
}
}11. Shiro与Spring Boot集成
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.14</version>
</dependency>
<!-- Shiro Spring Boot Starter -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.13.0</version>
</dependency>
</dependencies>创建ShiroConfig.java:
package com.example.config;
import com.example.realm.CustomRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* 配置Realm
*/
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
/**
* 配置SecurityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 配置ShiroFilter
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置登录URL
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置未授权跳转URL
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
// 配置URL过滤规则
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon"); // 匿名访问
filterChainDefinitionMap.put("/logout", "logout"); // 退出
filterChainDefinitionMap.put("/static/**", "anon"); // 静态资源
filterChainDefinitionMap.put("/**", "authc"); // 需要认证
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}package com.example.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return "登录成功";
} catch (Exception e) {
return "登录失败:" + e.getMessage();
}
}
@GetMapping("/user")
public String getUser() {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
return "当前用户:" + subject.getPrincipal();
}
return "未登录";
}
@GetMapping("/admin")
public String admin() {
Subject subject = SecurityUtils.getSubject();
subject.checkRole("admin");
return "管理员页面";
}
}12. Web应用集成
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- Shiro过滤器 -->
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Shiro监听器 -->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
</web-app>[main]
# 配置Realm
customRealm=com.example.realm.CustomRealm
securityManager.realms=$customRealm
# 配置URL过滤器
[urls]
/login = anon
/logout = logout
/static/** = anon
/api/** = authc
/admin/** = authc, roles[admin]anon:匿名访问authc:需要认证authcBasic:HTTP Basic认证logout:退出登录noSessionCreation:不创建会话perms[permission]:需要权限roles[role]:需要角色ssl:需要HTTPSuser:需要已认证或记住我
13. 缓存配置
频繁查询数据库会影响性能,使用缓存可以提高性能。
添加依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.13.0</version>
</dependency>配置缓存管理器
@Bean
public CacheManager cacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
securityManager.setCacheManager(cacheManager());
return securityManager;
}在Realm中启用缓存
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 授权信息会被缓存
// ...
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
// 认证信息默认不缓存
// ...
}
}14. 实际项目案例
实体类
public class User {
private Long id;
private String username;
private String password;
private String salt;
private List<Role> roles;
// getter/setter...
}
public class Role {
private Long id;
private String roleName;
private List<Permission> permissions;
// getter/setter...
}
public class Permission {
private Long id;
private String permission;
// getter/setter...
}Realm实现
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
User user = userService.findByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 设置角色
Set<String> roles = user.getRoles().stream()
.map(Role::getRoleName)
.collect(Collectors.toSet());
authorizationInfo.setRoles(roles);
// 设置权限
Set<String> permissions = user.getRoles().stream()
.flatMap(role -> role.getPermissions().stream())
.map(Permission::getPermission)
.collect(Collectors.toSet());
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String username = (String) token.getPrincipal();
User user = userService.findByUsername(username);
if (user == null) {
throw new UnknownAccountException("用户不存在");
}
return new SimpleAuthenticationInfo(
username,
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
}
}15. 最佳实践与常见问题
- 密码加密:始终存储加密后的密码
- 使用盐值:为每个用户生成唯一的盐值
- 权限粒度:合理设计权限粒度
- 缓存配置:合理配置缓存提高性能
- 异常处理:妥善处理认证和授权异常
- 会话管理:合理设置会话超时时间
Q1: 如何清除缓存?
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout(); // 退出时会清除缓存Q2: 如何实现"记住我"功能?
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);
currentUser.login(token);Q3: 如何实现单点登录(SSO)?
需要使用Shiro的Session共享功能,配合Redis等缓存实现。
Q4: 如何自定义认证逻辑?
继承AuthenticatingRealm或AuthorizingRealm,重写认证方法。
Q5: 如何实现动态权限?
在Realm的doGetAuthorizationInfo方法中动态查询权限。
16. 总结与进阶
通过本教程,你已经掌握了:
- ✅ Shiro的核心概念和架构
- ✅ 认证和授权的实现
- ✅ 自定义Realm的开发
- ✅ 密码加密和会话管理
- ✅ Spring和Spring Boot集成
- ✅ Web应用集成
- 源码学习:深入理解Shiro的实现原理
- 性能优化:缓存配置和性能调优
- 分布式会话:实现分布式环境下的会话共享
- 单点登录:实现SSO系统
- OAuth集成:集成OAuth2.0认证
- 官方文档:https://shiro.apache.org/
- GitHub:https://github.com/apache/shiro
- 示例项目:Shiro官方示例
结语
Apache Shiro是一个功能强大且易于使用的Java安全框架。通过本教程的学习,相信你已经掌握了Shiro的核心功能和使用方法。
记住:
- 安全第一:始终加密密码,合理设计权限
- 性能优化:合理使用缓存提升性能
- 持续学习:关注Shiro社区,学习最佳实践
祝你学习愉快,编程顺利! 🚀
本教程由Java突击队学习社区编写,如有问题欢迎反馈。