会话管理的相关API
Shiro提供了丰富的会话管理API,允许开发者对会话进行创建、获取、设置属性、删除属性、获取会话超时时间、设置会话超时时间等操作。
Subject.getSession()
:获取当前用户的会话。如果当前没有会话,则会创建一个新的会话。Session.setAttribute(key, value)
:设置会话属性。Session.getAttribute(key)
:获取会话属性。Session.removeAttribute(key)
:删除会话属性。Session.getTimeout()
:获取当前会话的超时时间(以毫秒为单位)。Session.setTimeout(timeout)
:设置当前会话的超时时间(以毫秒为单位)。
SessionDAO和会话的使用
SessionDAO是用于会话持久化的接口,Shiro提供了多种SessionDAO实现,如MemorySessionDAO(在内存中进行会话维护)、EnterpriseCacheSessionDAO(提供缓存功能的会话维护)等。
Serializable create(Session session)
:将一个新的会话持久化到存储设备。Session readSession(Serializable sessionId)
:根据会话ID从存储设备中获取会话。void update(Session session)
:更新会话信息,如更新会话的最后访问时间、设置超时时间等。void delete(Session session)
:删除会话。
在实际应用中,可以通过配置Shiro的sessionManager和sessionDAO来实现会话的持久化和管理。
缓存的问题分析和解决办法
在使用Shiro进行会话管理时,可能会遇到缓存相关的问题,如缓存失效、缓存不一致等。这些问题通常是由于缓存配置不当或缓存实现不支持某些操作导致的。
-
问题分析:
- 缓存失效:可能是由于缓存过期策略设置不当或缓存被手动清除导致的。
- 缓存不一致:可能是由于多个线程同时修改缓存数据而没有进行同步导致的。
-
解决办法:
- 确保缓存过期策略设置合理,避免缓存过早失效。
- 使用线程安全的缓存实现,或在修改缓存数据时进行同步处理。
- 定期检查缓存状态,及时发现并处理缓存问题。
Shiro加密功能
加密哈希与盐
为了保护用户密码等敏感信息,Shiro提供了加密哈希与盐的功能。通过哈希函数(如MD5、SHA-256等)和盐值(一个随机生成的字符串)的组合,可以将用户密码转换为不可逆的哈希值,从而确保密码在存储过程中的安全性。
加密与验证
在Shiro中,可以使用HashedCredentialsMatcher类来实现密码的加密与验证。该类提供了对密码进行哈希处理的功能,并在用户登录时验证输入的密码是否与存储的哈希值匹配。
- 加密实现:
在用户注册时,使用HashedCredentialsMatcher对密码进行哈希处理,并将哈希值和盐值存储在数据库中。
java
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("SHA-256"); // 设置哈希算法
matcher.setHashIterations(1024); // 设置哈希迭代次数
- 验证实现:
在用户登录时,从数据库中获取用户的哈希值和盐值,并使用HashedCredentialsMatcher对输入的密码进行哈希处理。然后,比较处理后的哈希值是否与数据库中的哈希值匹配。
java
AuthenticationInfo info = new SimpleAuthenticationInfo(username, hashedPassword, salt, realmName);
// ... 在登录验证过程中使用info对象进行密码比对
登录次数的限制
为了防止恶意用户通过暴力破解密码的方式攻击系统,Shiro提供了登录次数的限制功能。可以通过配置Shiro的Realm或自定义拦截器来实现该功能。
-
实现思路:
- 使用缓存(如Redis或Ehcache)来记录用户的登录次数。
- 在用户登录时,检查缓存中记录的登录次数是否超过限制。
- 如果超过限制,则拒绝用户的登录请求,并设置一定的锁定时间。
- 如果未超过限制,则允许用户登录,并更新缓存中的登录次数。
-
示例代码:
以下是一个使用Redis实现登录次数限制的示例代码:
java
// 自定义拦截器
public class LoginAttemptLimitFilter extends AccessControlFilter {
// ...
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
// 从请求中获取用户名
String username = (String) request.getAttribute("username");
// 从Redis中获取用户的登录次数
Jedis jedis = new Jedis("localhost");
String loginAttempts = jedis.get("loginAttempts:" + username);
if (loginAttempts != null && Integer.parseInt(loginAttempts) >= 5) {
// 登录次数超过限制,拒绝访问
return false;
}
// 允许访问,并更新登录次数
if (loginAttempts == null) {
jedis.set("loginAttempts:" + username, "1");
} else {
jedis.incr("loginAttempts:" + username);
}
return true;
}
// ...
}
// Shiro配置类
@Configuration
public class ShiroConfig {
// ...
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 配置登录URL、成功URL和未授权URL
shiroFilter.setLoginUrl("/login");
shiroFilter.setSuccessUrl("/home");
shiroFilter.setUnauthorizedUrl("/403");
// 配置自定义拦截器
Map<String, Filter> filters = new HashMap<>();
filters.put("loginAttemptLimit", new LoginAttemptLimitFilter());
shiroFilter.setFilters(filters);
// 配置URL过滤链
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/**", "authc, loginAttemptLimit");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}
// ...
}