简介
在 shiro 里所有用户的会话信息都会由 Shiro 来进行控制,它提供的会话可以用于JavaSE/JavaEE 环境,不依赖于任何底层容器,可以独立使用,是完整的会话模块。通过 Shiro 的会话管理器(SessionManager)进行统一的会话管理。
一、会话管理简介
SessionManager(会话管理器):管理所有 Subject 的 session 包括创建、维护、删除、失效、验证等工作。SessionManager是顶层组件(接口),具体的是由 SecurityManager 管理。
Shiro 提供了三个默认实现:
- DefaultSessionManager: 用于JavaSE环境
- ServletContainerSessionManager: 用于Web环境,直接使用servlet容器的会话。
- DefaultWebSessionManager: 用于web环境,自己维护会话(自己维护着会话,直接废弃了Servlet容器的会话管理)。
在web程序中(就是前几篇文章的例子),通过 shiro 的 Subject.login() 方法登录成功后,用户的认证信息实际上是保存在 HttpSession 中的。
二、Shiro结合redis的统一会话管理图示
三、Shiro结合redis的统一会话管理环境准备
- 主要依赖
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.0.0</version>
</dependency>
- 在配置文件中添加 redis 配置信息
redis:
host: 127.0.0.1
port: 6379
- 自定义 Shiro 会话管理器
/** 自定义的sessionManager */
public class CustomSessionManager extends DefaultWebSessionManager {
/**
* 头信息中具有sessionid 请求头:Authorization: sessionid
*
* <p>指定sessionId的获取方式
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
// 获取请求头Authorization中的数据
String id = WebUtils.toHttp(request).getHeader("Authorization");
if (StringUtils.isEmpty(id)) {
// 如果没有携带,生成新的sessionId
return super.getSessionId(request, response);
} else {
// 返回sessionId(固定写法)
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header"); //标记sessionId的来源
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); //当前sessionId具体的值
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); //sessionId需不需要验证
return id;
}
}
}
- 修改之前的Shiro配置类
@Configuration
public class ShiroConfiguration {
// 1.创建realm
@Bean
public CustomRealm getRealm() {
return new CustomRealm();
}
// 2.创建安全管理器
@Bean
public SecurityManager getSecurityManager(CustomRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
/*************添加Shiro会话管理配置*********************************/
// 将自定义的会话管理器注册到安全管理器中
securityManager.setSessionManager(sessionManager());
// 将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(cacheManager());
/*****************************************************************/
return securityManager;
}
// 3.配置shiro的过滤器工厂
/** 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制 */
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// -创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
// -设置安全管理器
filterFactory.setSecurityManager(securityManager);
// -通用配置(跳转登录页面,为授权跳转的页面)
filterFactory.setLoginUrl(url地址); // 设置没登陆时跳转url地址
filterFactory.setUnauthorizedUrl(url地址); // 设置未授权时跳转的url地址
// -设置过滤器集合
/** 设置所有的过滤器:选择有顺序map key = 拦截的url地址 value = 过滤器类型 */
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/home","anon"); //表示"/user/home"请求地址可以被匿名访问
filterMap.put("/user/**", "authc"); //表示"/user/"下的所有请求地址必须认证之后可以才被访问
// -将过滤器集合配置到filter中
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
// 4.开启对shior注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
/*************添加Shiro会话管理配置*********************************/
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
/** 1.redis的控制器,操作redis */
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
return redisManager;
}
/** 2.sessionDao */
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
/** 3.会话管理器 */
public DefaultWebSessionManager sessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/** 4.缓存管理器 */
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
- 修改实体类,实现 AuthCachePrincipal 接口
/** 用户实体类 */
@Entity
@Table(name = "pe_user")
@Getter
@Setter
/** AuthCachePrincipal: redis和shiro插件包提供的接口 */
public class User implements Serializable, AuthCachePrincipal {
private static final long serialVersionUID = 4297464181093070302L;
/** ID */
@Id private String id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "pe_user_role",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles = new HashSet<Role>(); // 用户与角色 多对多
@Override
public String getAuthCacheKey() {
return null;
}
}