会话管理
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如Tomcat),不管是J2SE还是J2EE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关的集群,失效/过期支持,对Web的透明支持,SSO单点登录的支持等特性。
会话相关API
- Subject.getSession(): 获取当前Subject的会话。如果当前没有会话,根据参数的不同会有不同的行为:
- Subject.getSession(true): 如果当前没有会话,则创建一个新的会话。
- Subject.getSession(false): 如果当前没有会话,则返回null。
- session.setAttribute(Object key, Object value): 设置会话属性。
- session.getAttribute(Object key)
:根据键获取会话属性。 - session.removeAttribute(Object key): 根据键删除会话属性。
SessionDAO
- AbstractSessionDAO类:这是SessionDAO的一个基本实现类,提供了生成会话ID等基础功能。
- CachingSessionDAO类:这是一个提供了缓存功能的实现类,它位于应用层与持久化层之间,用于减少频繁请求持久化层的开销。它重写了AbstractSessionDAO中的部分方法,并实现了SessionDAO中的其他方法。不过,CachingSessionDAO中的doUpdate和doDelete方法是预留给子类去实现的。
- MemorySessionDAO类:这是一个将Session保存在内存中的实现类,其存储结构是ConcurrentHashMap。不过,在实际项目中,这个实现类用得并不多。
- EnterpriseCacheSessionDAO类:这是一个提供了缓存功能的Session维护实现类。它设置了默认的缓存管理器(AbstractCacheManager)和默认的缓存实例(MapCache),并实现了缓存效果。不过,它并没有实现持久化操作,只是简单地提供了缓存实现。开发者可以继承这个类,并重写doCreate、doReadSession、doUpdate和doDelete等方法来实现持久化操作。
缓存
具体实现
1. 添加依赖
<!-- Shiro配置Redis缓存管理 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.3.1</version>
</dependency>
2. application.properties或 application.yml配置文件中添加Redis配置
spring:
#Redis配置
data:
redis:
# Redis 服务器的主机名或 IP 地址
host: localhost
# Redis 服务器的端口号,默认是 6379
port: 6379
# Redis 服务器密码
password: luoqiangwu
# Redis 的 lettuce 客户端连接池配置
lettuce:
pool:
# 连接池最大活动连接数,默认为 8
max-active: 8
# 连接池最大阻塞等待时间,-1 表示无限等待
max-wait: -1
# 连接池中的最大空闲连接数,默认为 8
max-idle: 8
# 连接池中最小空闲连接数,默认为 0
min-idle: 0
timeout: 5000
3. ShiroConfig
@Configuration
public class ShiroConfig {
// 注入Redis参数,从application.yml获得
@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;
@Resource
private RoleService roleService;
/**
* 开启Shiro注解(如@RequiresRoles,@RequiresPermissions),
* 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个 bean(DefaultAdvisorAutoProxyCreator 和 AuthorizationAttributeSourceAdvisor)
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启AOP注解支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 验证匹配规则
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 使用 md5 算法进行加密
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 设置散列次数:意为加密几次
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
/**
* 自定义Realm(基于数据库)
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm shiroRealm = new MyShiroRealm();
// 设置启用缓存,并设置缓存名称
shiroRealm.setCachingEnabled(true);
shiroRealm.setAuthorizationCachingEnabled(true);
shiroRealm.setAuthorizationCacheName("authorization");
// 设置凭证(密码)匹配器
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
/**
* Thymeleaf页面上使用shiro标签
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
/**
* Redis管理器
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host + ":" + port); // #shiro-redis v3.3.1
redisManager.setPassword(password);
redisManager.setTimeout(timeout);
return redisManager;
}
/**
* 缓存管理器
*/
@Bean
public CacheManager shiroCacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager());
// 缓存名称
cacheManager.setPrincipalIdFieldName("usrName");
// 缓存有效时间
cacheManager.setExpire(1800);
return cacheManager;
}
/**
* 会话持久化操作
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
/**
* 安全管理器SecurityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 注入自定义的Realm
securityManager.setRealm(myShiroRealm());
// 注入缓存管理器
securityManager.setCacheManager(shiroCacheManager());
// 注入会话管理器
securityManager.setSessionManager(sessionManager());
// Shiro2.0.1新版本需要SecurityUtils设置SecurityManager,否则报错(org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code)
SecurityUtils.setSecurityManager(securityManager);
return securityManager;
}
/**
* 会话管理
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* Shiro过滤器:权限验证
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
// 注入SecurityManager
shiroFilterFactory.setSecurityManager(securityManager);
// 权限验证:使用 Filter 控制资源(URL)的访问
shiroFilterFactory.setLoginUrl("/login"); // 登录页面URL
shiroFilterFactory.setSuccessUrl("/main"); // 登录成功URL
shiroFilterFactory.setUnauthorizedUrl("/403"); // 没有权限跳转403页面
// 权限配置集合,必须使用LinkedHashMap(有序集合)
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 静态资源授权
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/localcss/**", "anon");
filterChainDefinitionMap.put("/localjs/**", "anon");
// API接口请求授权
filterChainDefinitionMap.put("/api/**", "anon");
// 登录退出页面设置
filterChainDefinitionMap.put("/login", "anon"); // 点击登录按钮时放行
filterChainDefinitionMap.put("/logout", "logout"); // 点击退出按钮时放行
// 配置需要特定权限才能访问的资源(URL)
// 静态授权:包括全部需要特定权限才能访问的资源(URL)
/*
filterChainDefinitionMap.put("/user/list", "perms[用户列表]");
filterChainDefinitionMap.put("/user/add", "perms[用户添加]");
filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");
filterChainDefinitionMap.put("/user/del", "perms[用户删除]");
*/
// 动态授权
List<Right> rights = roleService.findAllRights();
for (Right right : rights) {
if (right.getRightUrl() != null && !right.getRightUrl().trim().equals("")) {
filterChainDefinitionMap.put(right.getRightUrl(), "perms[" + right.getRightCode() + "]");
}
}
// 配置认证访问:其他资源(URL)必须认证通过才能访问
filterChainDefinitionMap.put("/**", "authc"); // 必须放在过滤器链的最后面
shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactory;
}
}
加密
哈希与盐
如果你需要保存密码,你要考虑如何保护这些密码数据,像下面那样直接将密码写入数据库中是极不安全的,因为任何可以打开数据库的人,都将可以直接看到这些密码。
解决的办法是将密码加密后再存储进数据库,比较使用的加密方法是使用哈希函数(散列算法),常见的散列算法如MD5、SHA等。哈希函数的具体定义,大家可以在网上或者相关书籍中查询到,简单地说,它的特性如下:
- 原始密码经哈希函数计算后得到一个哈希值
- 改变原始密码,哈希函数计算出的哈希值也会相应改变
- 同样的密码,哈希值也是相同的
哈希函数是单向、不可逆的。也就是说从哈希值,你无法推算出原始的密码是多少。
加密与验证
Shiro提供了PasswordService及CredentialsMatcher用于提供加密密码及验证密码服务。
public interface PasswordService {
String encryptPassword(Object var1) throws IllegalArgumentException;
boolean passwordsMatch(Object var1, String var2);
}
具体实现
MyShiroRealm
public class MyShiroRealm extends AuthorizingRealm {
@Lazy // Shiro框架执行比@Cacheable注解AOP代理早,导致对象代理不成功
@Resource
private UserService userService;
@Lazy // Shiro框架执行比@Cacheable注解AOP代理早,导致对象代理不成功
@Resource
private RoleService roleService;
/**
* 自定义认证流程(机制)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("调用 MyShiroRealm.doGetAuthenticationInfo 获取身份信息!");
// 获得身份信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userName = token.getUsername();
// 从数据库中根据用户名查询用户对象(联表查询,包含角色信息)
User user = userService.findUserByUsrName(userName);
if (user == null) {
throw new UnknownAccountException("用户不存在"); // 用户不存在
}
if (user.getUsrFlag() == null || user.getUsrFlag() == 0) {
throw new LockedAccountException("账号已锁定");
}
// 通过用户获取角色信息
Role role = user.getRole(); // int &207 -> list
// 根据角色获取权限列表
Set<Right> rights = roleService.findRightByRoleId(role.getRoleId());
// 给角色设置权限集合
role.setRights(rights);
System.out.println(user);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getUsrPassword(), ByteSource.Util.bytes("czkt"), this.getName());
// 返回身份信息
return info;
}
/**
* 自定义授权(认证通过才会授权)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("调用 MyShiroRealm.doGetAuthorizationInfo 获取权限信息!");
// 获得权限信息
User user = (User) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 静态授权:授予主体(用户)相应的角色和权限
/*
info.addRole(user.getRole().getRoleName());
info.addStringPermission("用户列表"); // 所有用户拥有"用户列表"权限
if ("管理员".equals(user.getRole().getRoleName())) { // 管理员拥有"增删改"权限
info.addStringPermission("用户添加");
info.addStringPermission("用户编辑");
info.addStringPermission("用户删除");
}
*/
// 动态授权:从数据库中获取角色和权限
Role role = user.getRole();
if (role != null) {
info.addRole(role.getRoleName()); // 动态设置角色
Set<Right> rights = role.getRights();
if (rights != null && !rights.isEmpty()) {
for (Right right : rights) {
info.addStringPermission(right.getRightCode()); // 动态设置权限
}
}
}
// 返回授权信息
return info;
}
}

1105

被折叠的 条评论
为什么被折叠?



