1.会话管理
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如Tomcat),不管是J2SE还是J2EE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关的集群,失效/过期支持,对Web的透明支持,SSO单点登录的支持等特性。
1.1会话相关API
Subject.getSession(): 获取当前用户的会话,如果当前没有创建会话对象,则会创建一个新的会话。这等价于Subject.getSession(true)。
Subject.getSession(boolean create): 根据参数决定是否创建一个新的会话。如果create为true且当前没有会话,则创建一个新的会话;如果为false且当前没有会话,则返回null。
session.setAttribute(key, value): 设置会话属性。
session.getAttribute(key): 获取会话属性。
session.removeAttribute(key): 删除会话属性。
会话使用时,建议在Controller层使用原生的HttpSession对象,在Service层使用Shiro提供的Session对象。
1.2SeeionDAO
Shiro提供SessionDOA用于会话持久化提供CRUD操作。
AbstractSessionDAO:提供了SessionDAO 的基础实现,如生成会话ID等。
CachingSessionDAO :提供了对开发者透明的会话缓存的功能,需要设置相应的
CachingSessionDOA。
MemorySessionDA0:直接在内存中进行会话维护。
EnterpriseCacheSessionDA0:提供了缓存功能的会话维护,默认情况下使用MapCache 实现,内部使用ConcurrentHashMap保存缓存的会话。
在实际开发中,如果要用到SessionDA0组件,可以自定义类实现自EnterpriseCacheSessionDA0类,为其注入sessionldGenerator属性,如果用到缓存的话还可以注入一个缓存的实现。然后将这个 SesionDA0组件注入给SessionManager(会话管理器),最后将SessionManager 配置给 SecurityManager。下一小节我们将实现数据缓存,将使用到该组件。
2.缓存
1.添加依赖
2.配置application.properties配置文件中添加Redis配置
# Redis配置
data:
redis:
host: 127.0.0.1
port: 6379
password: 密码
database: 0
timeout: 5000
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制): 8
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)默认为-1
max-wait: -1
# 连接池中的最大空闲连接 默认为8
max-idle: 8
# 连接池中的最小空闲连接 默认为0
min-idle: 0
3.改造ShiroConfig
import com.bdqn.demo.pojo.Right;
import com.bdqn.demo.service.RightService;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/*
shiro安全框架配置类
*/
@Configuration
public class ShiroConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.password}")
private String password;
@Value("${spring.data.redis.timeout}")
private int timeout;
/**
* 开启shiro注解支持(如@RequiresRoles,@RequiresPermissions)
* 需要借助springAop扫描使用shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 配置shiro注解支持
* 开启shiro aop注解支持.
*
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host + ":" + port);
redisManager.setPassword(password);
redisManager.setTimeout(timeout);
return redisManager;
}
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
// 缓存名称
redisCacheManager.setPrincipalIdFieldName("usrName");
// 缓存有效时间
redisCacheManager.setExpire(1800);
return redisCacheManager;
}
// 会话操作
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
// 会话管理器
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* 加密算法
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2); // 散列的次数,比如散列两次
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm myShiroRealm() { // 自定义的realm
MyShiroRealm shiroRealm = new MyShiroRealm();
// 设置启用缓存,并设置缓存名称
shiroRealm.setCachingEnabled(true);
shiroRealm.setAuthorizationCachingEnabled(true);
shiroRealm.setAuthorizationCacheName("authorizationCache");
// 设置凭证(密码)匹配器
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
@Bean
public SecurityManager securityManager() { // 安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 注入Realm
securityManager.setRealm(myShiroRealm());
// 注入缓存管理器
securityManager.setCacheManager(cacheManager());
// 注入会话管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Resource
private RightService rightService;
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) { // shiro过滤器
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 注入SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 权限验证使用Filter控制资源(URL)的访问
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/main");
shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 没有权限跳转403页面
Map
// 配置可以匿名访问的资源
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/localcss/**", "anon");
filterChainDefinitionMap.put("/localjs/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/logout", "logout"); // 注销过滤器,自动注销
// 配置需要特定权限才能访问的资源(url)
// 动态授权:包括全部需要特定权限才能访问的资源(url)
List
for (Right right : rights) {
if (right.getRightUrl() != null && !"".equals(right.getRightUrl().trim())) {
filterChainDefinitionMap.put(right.getRightUrl().trim(), "perms[" + right.getRightCode() + "]");
String s = filterChainDefinitionMap.get(right.getRightUrl().trim());
System.out.println("动态权限:==" + right.getRightUrl().trim() + "===" + s);
}
}
// 配置认证访问:其他所有资源都需要认证才能访问
filterChainDefinitionMap.put("/**", "authc"); // 必须放在过滤器链的最后面
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
SecurityUtils.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect(); // 用于thymeleaf和shiro标签配合使用
}
}
3.加密
1. 加密概述
加密是保护数据安全的重要手段。Shiro提供了多种加密方式和工具,帮助开发者实现数据的加密和解密。
2. HashedCredentialsMatcher
HashedCredentialsMatcher是Shiro提供的一个用于密码加密和验证的工具。它支持多种哈希算法(如MD5、SHA-256等)和哈希迭代次数,以提高密码的安全性。在配置Realm时,可以定义一个HashedCredentialsMatcher属性,用于密码的加密和验证。
3. 盐值加密
为了提高密码的安全性,Shiro支持使用盐值进行加密。盐值是一个随机生成的字符串,与密码一起进行哈希运算,使得即使两个用户设置了相同的密码,生成的哈希值也是不同的。在Shiro中,盐值可以在返回AuthenticationInfo时带上,SecurityManager会根据提交的明文密码、加密算法、加密次数和盐值进行加密,然后与AuthenticationInfo中的密码进行比对。
4. 常见加密算法
Shiro支持多种常见的加密算法,如MD5、SHA-1、SHA-256等。MD5是一种广泛使用的哈希算法,但存在安全性问题,容易被破解。SHA-1和SHA-256等算法则相对更安全,适用于对安全性要求较高的场景。
4、Shiro会话管理与加密实践
1. 配置Shiro会话管理
在Shiro配置类中,可以配置会话管理器、会话监听器、会话存储等参数。例如,在Spring Boot环境中,可以通过Java配置类来配置Shiro的会话管理:
@Configuration
public class ShiroConfig {
// ...
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 配置会话超时时间等参数
sessionManager.setGlobalSessionTimeout(1800000); // 30分钟
// 配置会话DAO等
// sessionManager.setSessionDAO(...);
return sessionManager;
}
// ...
}
2. 配置密码加密
在配置Realm时,可以定义一个HashedCredentialsMatcher属性,用于密码的加密和验证。例如:
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("SHA-256"); // 设置哈希算法
hashedCredentialsMatcher.setHashIterations(1024); // 设置哈希迭代次数
return hashedCredentialsMatcher;
}
@Bean
public SecurityManager securityManager(CustomRealm customRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm);
securityManager.setCredentialsMatcher(hashedCredentialsMatcher()); // 设置密码加密验证器
return securityManager;
}
3. 自定义Realm
在自定义Realm中,可以实现doGetAuthenticationInfo方法,用于获取用户的认证信息,包括密码和盐值等。例如:
public class CustomRealm extends AuthorizingRealm {
// ...
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 查询用户信息,包括密码和盐值等
// User user = userService.findByUsername(username);
// 假设从数据库中查询到的密码和盐值
String password = "e10adc3949ba59abbe56e057f20f883e"; // md5(123456)
String salt = "lilei"; // 假设这个盐值是从数据库中查出的
ByteSource credentialsSalt = ByteSource.Util.bytes(salt);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, credentialsSalt, this.getName());
return info;
}
// ...
}