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和灵活的配置,成为中小型项目安全认证的首选。掌握其核心组件和配置方法,能够快速构建可靠的安全体系。