Shiro权限框架深度解析
Apache Shiro 是 Java 领域最经典的安全框架之一,本文档深入剖析 Shiro 的认证、授权、Session管理、加密机制,以及与Spring Boot的深度集成方案。
一、Shiro核心概念
1.1 三大核心组件
Apache Shiro
Subject 主体
SecurityManager 安全管理器
Realm 域
当前用户身份信息
登录/登出操作
权限检查
认证器 Authenticator
授权器 Authorizer
会话管理器 SessionManager
加密组件 CryptoSupport
JdbcRealm 数据库认证
IniRealm INI配置认证
TextConfigurationRealm
自定义Realm
1.2 Shiro架构图
Subject
+login(AuthenticationToken)
+logout()
+hasRole(String)
+isPermitted(String)
+getPrincipal()
SecurityManager
+authenticate(AuthenticationToken)
+authorize(PrincipalCollection)
+getSession()
Authenticator
+authenticate(AuthenticationToken)
Authorizer
+checkRole(PrincipalCollection, String)
+checkPermission(PrincipalCollection, String)
+hasRole(PrincipalCollection, String)
+isPermitted(PrincipalCollection, String)
SessionManager
+getSession()
+createSession()
Realm
+getAuthenticationInfo(AuthenticationToken)
+getAuthorizationInfo(PrincipalCollection)
SubjectAdapter
+SecurityManager securityManager
1.3 Shiro 与 Spring Security 对比
| 特性 | Shiro | Spring Security |
|---|---|---|
| 学习曲线 | 平缓 | 陡峭 |
| 文档完整性 | 一般 | 完善 |
| 社区活跃度 | 一般 | 活跃 |
| OAuth2支持 | 需要扩展 | 原生支持 |
| Session管理 | 自带 | 需Spring Session |
| 缓存支持 | Shiro-Cache | Spring Cache |
| 过滤器链 | 简单 | 复杂但强大 |
| 适用场景 | 中小型项目 | 企业级项目 |
二、认证流程深度解析
2.1 认证流程图
Realm Authenticator SecurityManager Subject 客户端 Realm Authenticator SecurityManager Subject 客户端 alt [认证成功] [认证失败] login(username, password) authenticate(token) authenticate(token) getAuthenticationInfo(token) SimpleAuthenticationInfo AuthenticationInfo 认证成功,存储SubjectContext 创建Session 抛出AuthenticationException 异常穿透 抛出AuthenticationException 认证失败
2.2 认证核心接口
java
// org.apache.shiro.authc.AuthenticationToken
public interface AuthenticationToken extends Serializable {
/**
* 获取主体(用户名)
*/
PrincipalCollection getPrincipal();
/**
* 获取凭证(密码)
*/
Object getCredentials();
}
java
// org.apache.shiro.authc.AuthenticationInfo
public interface AuthenticationInfo extends Serializable {
/**
* 获取主身份集合
*/
PrincipalCollection getPrincipals();
/**
* 获取凭证(已加密)
*/
Object getCredentials();
/**
* 凭证是否匹配
*/
default boolean isCredentialsMatch(AuthenticationToken token) {
return getCredentials.equals(token.getCredentials());
}
}
2.3 UsernamePasswordToken
java
// org.apache.shiro.authc.UsernamePasswordToken
public class UsernamePasswordToken implements HostAuthenticationToken {
private String username;
private char[] password;
private boolean rememberMe;
private String host;
public UsernamePasswordToken(String username, char[] password) {
this(username, password, false, null);
}
public UsernamePasswordToken(String username, String password,
boolean rememberMe, String host) {
this.username = username;
this.password = password != null ? password.toCharArray() : null;
this.rememberMe = rememberMe;
this.host = host;
}
// getters and setters
}
2.4 认证流程源码解析
java
// org.apache.shiro.mgt.ExecutingSecurityManager
public interface Authenticator {
/**
* 执行认证
* @param token 认证令牌
* @return 认证信息
* @throws AuthenticationException 认证异常
*/
AuthenticationInfo authenticate(AuthenticationToken token)
throws AuthenticationException;
/**
* 是否支持该Token类型
*/
boolean supports(AuthenticationToken token);
}
java
// org.apache.shiro.mgt.ModularRealmAuthenticator 核心实现
public class ModularRealmAuthenticator extends AbstractAuthenticator {
private Collection<Realm> realms;
private AuthenticationStrategy authenticationStrategy;
@Override
protected AuthenticationInfo doAuthenticate(
AuthenticationToken token) throws AuthenticationException {
// 1. 确保只有一个Realm被调用(单Realm场景)
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthenticate(token, realms.iterator().next());
}
// 2. 多Realm场景
return doMultiRealmAuthenticate(token, realms);
}
private AuthenticationInfo doSingleRealmAuthenticate(
AuthenticationToken token, Realm realm) {
if (!realm.supports(token)) {
throw new UnsupportedTokenException(
"Realm [" + realm + "] does not support authentication token [" +
token + "]");
}
// 调用Realm获取认证信息
AuthenticationInfo info = realm.getAuthenticationInfo(token);
return info;
}
private AuthenticationInfo doMultiRealmAuthenticate(
AuthenticationToken token, Collection<Realm> realms) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
// 使用策略进行多Realm认证
AuthenticationInfo aggregate = strategy.beforeAllAttempts(
realms, token);
for (Realm realm : realms) {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
AuthenticationInfo info = realm.getAuthenticationInfo(token);
aggregate = strategy.afterAttempt(realm, token, info, aggregate);
}
}
return strategy.afterAllAttempts(token, aggregate);
}
}
三、授权流程深度解析
3.1 授权核心接口
java
// org.apache.shiro.authz.Authorizer
public interface Authorizer {
/**
* 检查是否拥有指定角色
*/
boolean hasRole(PrincipalCollection principals, String roleIdentifier);
/**
* 检查是否拥有所有指定角色
*/
boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers);
/**
* 检查是否拥有指定权限
*/
boolean isPermitted(PrincipalCollection principals, String permission);
/**
* 检查是否拥有所有指定权限
*/
boolean isPermittedAll(PrincipalCollection principals, String... permissions);
/**
* 权限检查,若无权限则抛出异常
*/
void checkPermission(PrincipalCollection principals, String permission)
throws AuthorizationException;
/**
* 角色检查,若无角色则抛出异常
*/
void checkRole(PrincipalCollection principals, String roleIdentifier)
throws AuthorizationException;
}
3.2 授权流程图
权限缓存 Realm Authorizer SecurityManager Subject 权限缓存 Realm Authorizer SecurityManager Subject alt [缓存命中] [缓存未命中] hasRole/isPermitted 权限检查 获取AuthorizationInfo 直接返回权限 查询数据库 存入缓存 返回权限 true/false
3.3 ModularRealmAuthorizer 源码解析
java
// org.apache.shiro.mgt.ModularRealmAuthorizer
public class ModularRealmAuthorizer implements Authorizer,
ConfigurableSecurityManager {
private Collection<Realm> realms;
@Override
public boolean hasRole(PrincipalCollection principals, String role) {
assertRealmsConfigured();
for (Realm realm : realms) {
if (!(realm instanceof Authorizer)) {
continue;
}
Authorizer authorizer = (Authorizer) realm;
if (authorizer.hasRole(principals, role)) {
return true;
}
}
return false;
}
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : realms) {
if (!(realm instanceof Authorizer)) {
continue;
}
Authorizer authorizer = (Authorizer) realm;
if (authorizer.isPermitted(principals, permission)) {
return true;
}
}
return false;
}
}
四、Realm体系深度解析
4.1 Realm接口定义
java
// org.apache.shiro.realm.Realm
public interface Realm {
/**
* Realm的唯一标识
*/
String getName();
/**
* 是否支持该认证Token
*/
boolean supports(AuthenticationToken token);
/**
* 获取认证信息(密码验证)
*/
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException;
}
java
// org.apache.shiro.realm.AuthorizingRealm
public abstract class AuthorizingRealm extends AuthenticatingRealm
implements Authorizer {
/**
* 获取授权信息(角色和权限)
*/
protected abstract AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals);
/**
* 密码匹配器
*/
private CredentialsMatcher credentialsMatcher;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// 子类实现,从数据源获取认证信息
return null;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// 子类实现,从数据源获取授权信息
return null;
}
}
4.2 自定义Realm实现
java
// 自定义用户Realm
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// 1. 获取登录用户名
String username = principals.getPrimaryPrincipal().toString();
// 2. 查询用户信息
User user = userService.findByUsername(username);
if (user == null) {
throw new UnknownAccountException("User not found: " + username);
}
// 3. 查询角色和权限
Set<String> roles = roleService.findRoleNamesByUserId(user.getId());
Set<String> permissions = permissionService.findPermissionsByUserId(user.getId());
// 4. 构建AuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 1. 查询用户
User user = userService.findByUsername(upToken.getUsername());
if (user == null) {
throw new UnknownAccountException("User not found");
}
// 2. 检查账户状态
if (user.isLocked()) {
throw new LockedAccountException("Account is locked");
}
if (!user.isEnabled()) {
throw new DisabledAccountException("Account is disabled");
}
// 3. 返回认证信息(包含加密密码)
return new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
}
4.3 JdbcRealm数据库认证
java
// JdbcRealm配置示例
public class JdbcRealmConfig {
@Bean
public JdbcRealm jdbcRealm(DataSource dataSource) {
JdbcRealm realm = new JdbcRealm();
realm.setDataSource(dataSource);
realm.setPermissionsLookupEnabled(true);
// 认证查询
realm.setAuthenticationQuery(
"SELECT password, salt FROM sys_user WHERE username = ? AND enabled = 1");
// 角色查询
realm.setUserRolesQuery(
"SELECT r.role_name FROM sys_role r " +
"INNER JOIN sys_user_role ur ON r.id = ur.role_id " +
"INNER JOIN sys_user u ON u.id = ur.user_id " +
"WHERE u.username = ?");
// 权限查询
realm.setPermissionsQuery(
"SELECT p.permission FROM sys_permission p " +
"INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " +
"INNER JOIN sys_role r ON r.id = rp.role_id " +
"INNER JOIN sys_user_role ur ON r.id = ur.role_id " +
"INNER JOIN sys_user u ON u.id = ur.user_id " +
"WHERE u.username = ?");
return realm;
}
}
4.4 IniRealm配置认证
ini
[users]
# 用户名=密码,角色1,角色2
admin=admin123,admin,user
user1=user123,user
guest=guest123,guest
[roles]
# 角色名=权限1,权限2
admin=system:*,user:*,role:*
user=user:view,user:edit
guest=guest:view
[urls]
# URL路径=角色
/admin/**=authc,admin
/user/**=authc,user
/guest/**=authc
/public/**=anon
五、权限标识WildcardPermission
5.1 权限格式
Shiro使用WildcardPermission支持通配符权限:
resource:action:instance
| 示例 | 说明 |
|---|---|
| user:view | view权限,作用于所有user实例 |
| user:view:1 | view权限,作用于id=1的user |
| user:*:1 | 所有权限,作用于id=1的user |
| system:* | system模块的所有权限 |
| : | 全局所有权限 |
5.2 权限匹配规则
java
@Test
public void testWildcardPermission() {
// 精确匹配
Permission p1 = new WildcardPermission("user:view:1");
assertTrue(p1.implies(new WildcardPermission("user:view:1")));
// 通配符匹配
Permission p2 = new WildcardPermission("user:view:*");
assertTrue(p2.implies(new WildcardPermission("user:view:1")));
assertTrue(p2.implies(new WildcardPermission("user:view:2")));
// 角色权限验证
Permission adminPermission = new WildcardPermission("system:user:*,role:*");
// 验证:用户管理权限
assertTrue(adminPermission.implies(
new WildcardPermission("system:user:create")));
assertTrue(adminPermission.implies(
new WildcardPermission("system:user:delete:5")));
// 验证:角色管理权限
assertTrue(adminPermission.implies(
new WildcardPermission("system:role:assign")));
// 验证:不匹配的权限
assertFalse(adminPermission.implies(
new WildcardPermission("system:config:update")));
}
5.3 多级权限配置
java
@Configuration
public class ShiroConfig {
@Bean
public Realm customRealm() {
CustomRealm realm = new CustomRealm();
// 设置权限匹配器
realm.setPermissionResolver(new WildcardPermissionResolver());
// 设置角色匹配器
realm.setRolePermissionResolver(new ChainablePermissionResolver(
new WildcardPermissionResolver(),
new SimplePermissionResolver()));
// 启用缓存
realm.setCachingEnabled(true);
realm.setAuthorizationCachingEnabled(true);
return realm;
}
}
六、Session管理
6.1 Session接口
java
// org.apache.shiro.session.Session
public interface Session {
/**
* 获取会话ID
*/
Serializable getId();
/**
* 获取会话开始时间
*/
Date getStartTimestamp();
/**
* 获取最后访问时间
*/
Date getLastAccessTime();
/**
* 超时时间(毫秒)
*/
long getTimeout();
/**
* 设置超时时间
*/
void setTimeout(long maxIdleTimeInMillis);
/**
* 停止会话
*/
void stop();
/**
* 获取属性
*/
Object getAttribute(Object key);
/**
* 设置属性
*/
void setAttribute(Object key, Object value);
/**
* 删除属性
*/
Object removeAttribute(Object key);
}
6.2 SessionManager体系
<<interface>>
SessionManager
+start()
+getSession()
<<abstract>>
ValidatingSessionManager
+start()
+getSession()
ServletContainerSessionManager
// 使用Servlet容器的Session
DefaultSessionManager
// 默认的Session管理
EnterpriseCacheSessionManager
// 企业缓存Session管理
6.3 Shiro与Spring Session集成
java
@Configuration
public class ShiroSessionConfig {
@Autowired
private RedisConnectionFactory connectionFactory;
@Bean
public SessionDAO sessionDAO() {
// RedisSessionDAO使用Redis存储Session
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
sessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
// 设置过期时间
sessionDAO.setExpireSeconds(1800); // 30分钟
return sessionDAO;
}
@Bean
public RedisManager redisManager() {
RedisManager manager = new RedisManager();
manager.setHost("localhost:6379");
manager.setTimeout(3000);
manager.setPassword("redis-password");
return manager;
}
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(sessionDAO());
// Session验证间隔
sessionManager.setSessionValidationScheduler(
new ExecutorServiceSessionValidationScheduler(sessionManager));
sessionManager.setSessionValidationInterval(3600000); // 1小时
// 全局Session超时
sessionManager.setGlobalSessionTimeout(1800000); // 30分钟
// 删除无效Session
sessionManager.setDeleteInvalidSessions(true);
return sessionManager;
}
}
七、加密机制
7.1 Hash算法
java
// 常用Hash加密
@Test
public void testHashAlgorithm() {
String password = "admin123";
String salt = "random-salt";
// MD5加密(已不推荐)
Md5Hash md5Hash = new Md5Hash(password, salt, 2);
System.out.println("MD5: " + md5Hash.toHex());
// SHA-256加密
Sha256Hash sha256Hash = new Sha256Hash(password, salt, 1024);
System.out.println("SHA-256: " + sha256Hash.toHex());
// SHA-512加密
Sha512Hash sha512Hash = new Sha512Hash(password, salt, 4096);
System.out.println("SHA-512: " + sha512Hash.toHex());
// 通用Hash请求
Hash hash = new SimpleHash("SHA-256", password, salt, 1024);
System.out.println("Generic: " + hash.toHex());
}
7.2 密码加密服务
java
@Service
public class PasswordHashService {
private final HashService hashService;
@PostConstruct
public void init() {
// 配置Hash服务
DefaultHashService hashService = new DefaultHashService();
hashService.setHashAlgorithmName("SHA-256");
hashService.setPrivateSalt(Bytes.random(16)); // 私盐
hashService.setGeneratePublicSalt(true); // 生成公盐
hashService.setHashIterations(1024);
this.hashService = hashService;
}
/**
* 加密密码
*/
public String hashPassword(String plaintext) {
HashRequest request = new HashRequest.Builder()
.setSource(plaintext)
.build();
return hashService.computeHash(request).toHex();
}
/**
* 验证密码
*/
public boolean verifyPassword(String plaintext, String hashed) {
HashRequest request = new HashRequest.Builder()
.setSource(plaintext)
.setHash(hashed)
.build();
HashResponse response = hashService.computeHash(request);
return response.isHashValid();
}
}
7.3 CredentialsMatcher
java
// 自定义凭证匹配器
public class CustomCredentialsMatcher extends HashMatcher {
public CustomCredentialsMatcher() {
super();
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token,
AuthenticationInfo info) {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAuthenticationInfo simpleInfo = (SimpleAuthenticationInfo) info;
// 1. 获取提交的密码
String submittedPassword = new String(upToken.getPassword());
// 2. 获取存储的密码
String storedPassword = simpleInfo.getCredentials().toString();
// 3. 使用BCrypt验证
return BCrypt.checkpw(submittedPassword, storedPassword);
}
}
八、Spring Boot集成
8.1 依赖配置
xml
<!-- pom.xml -->
<dependencies>
<!-- Shiro核心 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.13.0</version>
</dependency>
<!-- Shiro Spring集成 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.13.0</version>
</dependency>
<!-- Shiro Web支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.13.0</version>
</dependency>
<!-- Shiro Cache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cache</artifactId>
<version>1.13.0</version>
</dependency>
<!-- Shiro Session Redis -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-redis</artifactId>
<version>4.5.3</version>
</dependency>
<!-- ehcache缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
</dependencies>
8.2 ShiroConfig配置类
java
@Configuration
public class ShiroConfig {
@Bean
public SecurityManager securityManager(
Realm customRealm,
SessionManager sessionManager,
CacheManager cacheManager) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(customRealm);
manager.setSessionManager(sessionManager);
manager.setCacheManager(cacheManager);
// 设置SubjectDAO
SubjectDAO subjectDAO = new DefaultSubjectDAO();
subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
manager.setSubjectDAO(subjectDAO);
return manager;
}
@Bean
public DefaultWebSessionStorageEvaluator sessionStorageEvaluator() {
DefaultWebSessionStorageEvaluator evaluator =
new DefaultWebSessionStorageEvaluator();
evaluator.setSessionStorageEnabled(false); // 禁用Session存储
return evaluator;
}
@Bean
public Realm customRealm(CredentialsMatcher credentialsMatcher) {
CustomRealm realm = new CustomRealm();
realm.setName("customRealm");
realm.setCachingEnabled(true);
realm.setAuthorizationCachingEnabled(true);
realm.setCredentialsMatcher(credentialsMatcher);
return realm;
}
@Bean
public CredentialsMatcher credentialsMatcher() {
// 使用Hash凭证匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("SHA-256");
matcher.setHashIterations(1024);
matcher.setStoredCredentialsHexEncoded(false);
return matcher;
}
}
8.3 ShiroFilter配置
java
@Configuration
public class ShiroFilterConfig {
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition =
new DefaultShiroFilterChainDefinition();
// 匿名访问路径
definition.addPathDefinition("/public/**", "anon");
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/api/auth/**", "anon");
// 需要认证的路径
definition.addPathDefinition("/user/**", "authc");
definition.addPathDefinition("/admin/**", "authc,roles[admin]");
// 登出
definition.addPathDefinition("/logout", "logout");
// 默认跳转
definition.addPathDefinition("/**", "authc");
return definition;
}
}
8.4 Controller登录实现
java
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private SecurityManager securityManager;
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
// 1. 创建Token
UsernamePasswordToken token = new UsernamePasswordToken(
request.getUsername(),
request.getPassword().toCharArray()
);
token.setRememberMe(request.isRememberMe());
token.setHost(request.getIp());
// 2. 获取Subject
Subject subject = SecurityUtils.getSubject();
try {
// 3. 执行登录
subject.login(token);
// 4. 返回结果
return Result.success(Map.of(
"username", subject.getPrincipal(),
"sessionId", subject.getSession().getId()
));
} catch (UnknownAccountException e) {
return Result.fail("用户名不存在");
} catch (IncorrectCredentialsException e) {
return Result.fail("密码错误");
} catch (LockedAccountException e) {
return Result.fail("账户已被锁定");
} catch (AuthenticationException e) {
return Result.fail("认证失败: " + e.getMessage());
}
}
@PostMapping("/logout")
public Result logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return Result.success("已退出登录");
}
}
8.5 注解式权限控制
java
@Configuration
@EnableAspectJAutoProxy
public class AopAnnotationsConfig {
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor =
new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
@Service
public class UserService {
/**
* 需要admin角色
*/
@RequiresRoles("admin")
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
/**
* 需要user:view权限
*/
@RequiresPermissions("user:view")
public User getUser(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
}
/**
* 需要admin和user角色
*/
@RequiresRoles(value = {"admin", "user"}, logical = Logical.AND)
public void updateUser(Long userId, UserUpdateRequest request) {
// 更新逻辑
}
/**
* 需要通过权限检查
*/
@RequiresAuthentication
public UserProfile getProfile() {
Subject subject = SecurityUtils.getSubject();
String username = subject.getPrincipal().toString();
return userService.findByUsername(username);
}
}
8.6 全局异常处理
java
@RestControllerAdvice
public class ShiroExceptionHandler {
@ExceptionHandler(UnknownAccountException.class)
public Result handleUnknownAccount(UnknownAccountException e) {
return Result.fail("账户不存在");
}
@ExceptionHandler(IncorrectCredentialsException.class)
public Result handleIncorrectCredentials(IncorrectCredentialsException e) {
return Result.fail("密码错误");
}
@ExceptionHandler(LockedAccountException.class)
public Result handleLockedAccount(LockedAccountException e) {
return Result.fail("账户已被锁定");
}
@ExceptionHandler(UnauthorizedException.class)
public Result handleUnauthorized(UnauthorizedException e) {
return Result.fail("无权限访问");
}
@ExceptionHandler(AuthorizationException.class)
public Result handleAuthorization(AuthorizationException e) {
return Result.fail("权限不足");
}
}
九、缓存配置
9.1 Ehcache配置
java
@Configuration
public class ShiroCacheConfig {
@Bean
public CacheManager cacheManager() {
// 使用Ehcache
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFilePath("classpath:ehcache.xml");
return cacheManager;
}
}
xml
<!-- ehcache.xml -->
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="false">
<!-- 认证缓存 -->
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"/>
<!-- 授权缓存 -->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"/>
<!-- Session缓存 -->
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="0"
overflowToDisk="false"/>
</ehcache>
9.2 Redis缓存配置
java
@Configuration
public class ShiroRedisCacheConfig {
@Bean
public CacheManager shiroRedisCacheManager(
RedisTemplate<String, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager());
// 缓存过期时间
cacheManager.setExpire(1800); // 30分钟
// 缓存键前缀
cacheManager.setKeyPrefix("shiro:cache:");
return cacheManager;
}
@Bean
public RedisManager redisManager() {
RedisManager manager = new RedisManager();
manager.setHost("localhost:6379");
manager.setTimeout(3000);
return manager;
}
}
十、过滤器链
10.1 常用过滤器
| 过滤器 | 说明 | 用法 |
|---|---|---|
| anon | 匿名访问 | anon |
| authc | 需要认证 | authc |
| authcBasic | HTTP Basic认证 | authcBasic |
| user | 认证或记住我 | user |
| logout | 登出 | logout |
| perms | 权限检查 | perms[user:view] |
| roles | 角色检查 | roles[admin,user] |
| ssl | 需要HTTPS | ssl |
| port | 端口检查 | port[8080] |
10.2 自定义过滤器
java
// JWT认证过滤器
public class JwtAuthFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(
HttpServletRequest request,
HttpServletResponse response) throws Exception {
String token = getToken(request);
if (token == null || token.isEmpty()) {
return null;
}
// 解析JWT获取用户信息
Claims claims = jwtService.parseToken(token);
return new JwtToken(
claims.getSubject(),
token,
claims.getIssuedAt().getTime()
);
}
@Override
protected boolean onAccessDenied(
HttpServletRequest request,
HttpServletResponse response) throws Exception {
String token = getToken(request);
if (token == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 尝试登录
return executeLogin(request, response);
}
private String getToken(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
}
10.3 过滤器配置
java
@Configuration
public class ShiroFilterConfig {
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition =
new DefaultShiroFilterChainDefinition();
// API路径 - JWT认证
definition.addPathDefinition("/api/**", "jwtAuth,noSessionCreation,authc");
// 静态资源 - 匿名访问
definition.addPathDefinition("/static/**", "anon");
definition.addPathDefinition("/public/**", "anon");
// 登录相关 - 匿名访问
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/auth/login", "anon");
// 登出
definition.addPathDefinition("/logout", "logout");
// 管理后台 - 角色控制
definition.addPathDefinition("/admin/**", "authc,roles[admin]");
// 其他 - 需认证
definition.addPathDefinition("/**", "authc");
return definition;
}
@Bean
public JwtAuthFilter jwtAuthFilter() {
return new JwtAuthFilter();
}
}
十一、RememberMe功能
11.1 RememberMe配置
java
@Configuration
public class ShiroConfig {
@Bean
public SecurityManager securityManager(..., RememberMeManager rememberMeManager) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// ...
manager.setRememberMeManager(rememberMeManager);
return manager;
}
@Bean
public RememberMeManager rememberMeManager() {
CookieRememberMeManager manager = new CookieRememberMeManager();
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(86400 * 7); // 7天
cookie.setHttpOnly(true);
cookie.setSameSite("Strict");
manager.setCookie(cookie);
// 设置加密密钥
manager.setCipherKey(Base64.decode(
"shiroRememberMeCipherKey=="));
return manager;
}
}
11.2 使用RememberMe
java
// 登录时启用RememberMe
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
UsernamePasswordToken token = new UsernamePasswordToken(
request.getUsername(),
request.getPassword().toCharArray()
);
// 启用RememberMe
token.setRememberMe(true);
Subject subject = SecurityUtils.getSubject();
subject.login(token);
return Result.success();
}
// 检查是否RememberMe登录
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated() || subject.isRemembered()) {
// 已登录或RememberMe登录
}
十二、多Realm认证
12.1 多Realm配置
java
@Configuration
public class ShiroConfig {
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 配置多个Realm
List<Realm> realms = new ArrayList<>();
realms.add(jdbcRealm());
realms.add(oauth2Realm());
realms.add(ldapRealm());
// 使用认证策略
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setRealms(realms);
// 认证策略:第一个成功的优先
authenticator.setAuthenticationStrategy(
new FirstSuccessfulAuthenticationStrategy());
// 或者:至少一个成功
// authenticator.setAuthenticationStrategy(
// new AtLeastOneSuccessfulAuthenticationStrategy());
// 或者:全部成功
// authenticator.setAuthenticationStrategy(
// new AllSuccessfulAuthenticationStrategy());
manager.setAuthenticator(authenticator);
return manager;
}
}
12.2 认证策略对比
| 策略 | 说明 | 使用场景 |
|---|---|---|
| FirstSuccessfulAuthenticationStrategy | 第一个成功即返回 | 单一数据源 |
| AtLeastOneSuccessfulAuthenticationStrategy | 至少一个成功 | 多数据源,任一可认证 |
| AllSuccessfulAuthenticationStrategy | 全部成功 | 高安全要求 |
十三、总结
13.1 核心流程
anon
authc
用户名密码
OAuth
JWT
成功
失败
命中
未命中
请求到达
路径匹配
匿名访问
创建Subject
认证Token
JdbcRealm / 自定义Realm
OAuth2Realm
JwtRealm
密码匹配
获取AuthorizationInfo
抛出异常
权限缓存
返回缓存
查询数据库
权限验证
返回错误
通过/拒绝
13.2 配置检查清单
| 检查项 | 说明 |
|---|---|
| Realm配置 | 确保实现正确的Realm |
| CredentialsMatcher | 密码加密算法配置 |
| SessionManager | 分布式Session配置 |
| CacheManager | 权限缓存配置 |
| FilterChain | 路径过滤规则 |
| RememberMe | Cookie安全配置 |
13.3 常见问题
| 问题 | 解决方案 |
|---|---|
| 认证失败无响应 | 检查Realm的supports()方法 |
| 权限缓存无效 | 检查CacheManager配置 |
| Session丢失 | 检查SessionManager配置 |
| RememberMe不生效 | 检查Cookie的HttpOnly配置 |
Shiro 作为轻量级安全框架,以其简洁的API和灵活的配置,成为中小型项目安全认证的首选。掌握其核心组件和配置方法,能够快速构建可靠的安全体系。