Shiro 中的 SessionManager

shiro提供了完整的会话管理功能,不依赖底层容器,JavaSE应用和JavaEE应用都可以使用。SessionManager(会话管理器)管理着应用中所有Subject的会话,包括会话的创建、维护、删除、失效、验证等工作。

SessionManager继承结构

在这里插入图片描述
DefaultSecurityManager默认使用的SessionManager为DefaultSessionManager,用于JavaSE环境的session管理。

SessionManager 接口

SessionManager 接口是Shiro所有会话管理器的顶级接口。在此接口中声明了两个方法Session start(SessionContext context);和Session getSession(SessionKey key) throws SessionException;。

Session start(SessionContext context);方法,基于指定的上下文初始化数据启动新会话。
Session getSession(SessionKey key) throws SessionException; 根据指定的SessionKey检索会话,如果找不到则返回null。如果找到了会话,但会话但无效(已停止或已过期)则抛出SessionException异常。

扩展注意

如果要从新实现自己的会话管理器,建议扩展 AbstractValidationSessionManager 抽象类。
如果要在原有会话管理功能的基础上进行扩展,在桌面应用中建议扩展DefaultSessionManager,在Web环境下建议扩展DefaultWebSessionManager。
stat 方法 和 getSession 方法
需要重点说一下 getSession 方法,是因为大多数扩展中都是围绕它展开的。至于start方法,Shiro中自带的实现已经可以满足我们的需求。

Shiro 在三个地方对 start 进行了实现:

// AbstractNativeSessionManager 抽象类中的实现
public Session start(SessionContext context) {
    Session session = createSession(context);
    applyGlobalSessionTimeout(session);
    onStart(session, context);
    notifyStart(session);
    //Don't expose the EIS-tier Session object to the client-tier:
    return createExposedSession(session, context);
}

// ServletContainerSessionManager 中的实现
public Session start(SessionContext context) throws AuthorizationException {
    return createSession(context);
}

// SessionsSecurityManager 中的实现
public Session start(SessionContext context) throws AuthorizationException {
    return this.sessionManager.start(context);
}

由以上代码可知,SessionsSecurityManager中的实现其实没有实际的代码,而是直接调用的SessionManager对象的start方法(其实就是前两个实现中的一个)。两个有意义的start方法的实现在扩展中我们都无需要关心,直接继承就ok了。

Shiro 在同样在三个地方对getSession方法进行了实现:

// AbstractNativeSessionManager 抽象类中的实现
public Session getSession(SessionKey key) throws SessionException {
    Session session = lookupSession(key);
    return session != null ? createExposedSession(session, key) : null;
}

// ServletContainerSessionManager 中的实现
public Session getSession(SessionKey key) throws SessionException {
    if (!WebUtils.isHttp(key)) {
        String msg = "SessionKey must be an HTTP compatible implementation.";
        throw new IllegalArgumentException(msg);
    }
    HttpServletRequest request = WebUtils.getHttpRequest(key);
    Session session = null;
    HttpSession httpSession = request.getSession(false);
    if (httpSession != null) {
        session = createSession(httpSession, request.getRemoteHost());
    }
    return session;
}

// SessionsSecurityManager 中的实现
public Session getSession(SessionKey key) throws SessionException {
    return this.sessionManager.getSession(key);
}

同样的SessionsSecurityManager中的实现其实没有实际的代码,而是直接调用的SessionManager对象的getSession方法。由于ServletContainerSessionManager中直接使用了HttpServletSession,所以一般也不会对其进行扩展。我们扩展过程中需要关心的主要是AbstractNativeSessionManager中的getSession。

从上述源码中可以看到,lookupSession方法,才是用来获取Session的方法。

private Session lookupSession(SessionKey key) throws SessionException {
    if (key == null) {
        throw new NullPointerException("SessionKey argument cannot be null.");
    }
    return doGetSession(key);
}

private Session lookupRequiredSession(SessionKey key) throws SessionException {
    Session session = lookupSession(key);
    if (session == null) {
        String msg = "Unable to locate required Session instance based on SessionKey [" + key + "].";
        throw new UnknownSessionException(msg);
    }
    return session;
}

protected abstract Session doGetSession(SessionKey key) throws InvalidSessionException;

lookupSession方法又把获取Session的操作交给了抽象方法doGetSession。而doGetSession仅有一个实现在AbstractValidatingSessionManager中如下:

@Override
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
    enableSessionValidationIfNecessary();
    log.trace("Attempting to retrieve session with key {}", key);
    Session s = retrieveSession(key);
    if (s != null) {
        validate(s, key);
    }
    return s;
}

protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException;

真是不让人省心,在doGetSession中又把获取Session的关键性操作交给了抽象方法retrieveSession。retrieveSession也仅有一个实现在DefaultSessionManager中,如下所示:

protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
    Serializable sessionId = getSessionId(sessionKey);
    if (sessionId == null) {
        log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a " +
                "session could not be found.", sessionKey);
        return null;
    }
    Session s = retrieveSessionFromDataSource(sessionId);
    if (s == null) {
        //session ID was provided, meaning one is expected to be found, but we couldn't find one:
        String msg = "Could not find session with ID [" + sessionId + "]";
        throw new UnknownSessionException(msg);
    }
    return s;
}

protected Serializable getSessionId(SessionKey sessionKey) {
    return sessionKey.getSessionId();
}

protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
    return sessionDAO.readSession(sessionId);
}

从上面的源码中,可以看出来获取Session的关键在于getSessionId方法。

其实对于DefaultSessionManager也没什么好扩展的。Shiro中的实现已经可以满足我们的大多数需求。真正需要扩展getSession方法功能的是在DefaultWebSessionManager中。在DefaultWebSessionManager没有getSession方法的实现,但它对getSessionId方法进行了重写:

@Override
public Serializable getSessionId(SessionKey key) {
    Serializable id = super.getSessionId(key);
    if (id == null && WebUtils.isWeb(key)) {
        ServletRequest request = WebUtils.getRequest(key);
        ServletResponse response = WebUtils.getResponse(key);
        id = getSessionId(request, response);
    }
    return id;
}

在Web环境下,请求到来时的第一次Serializable id = super.getSessionId(key);得到的是null,因为这时是一个新的线程再处理请求。后续再调用super.getSessionId(key);时,就可以正常拿到SessionId了。

所以对SessionManager的扩展多数是在Web环境下才需要,一般也是继承DefaultWebSessionManager然后重写getSessionId方法。

扩展举例

比如我们在为App接口做Web应用时,想使用Session功能时。因为App端的请求一般不会设置Cookies,所以我们需要使用Token(一般存储的HTTP请求头中)来做SessionId。

这时我们可以继承DefaultWebSessionManager实现自己的SessionManager类,然后重写getSessionId方法如下:

public class MySessionManager extends DefaultWebSessionManager {
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader("token");
    
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request");
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shiro SessionManager 负责管理用户的会话信息,其主要职责包括创建、获取、删除和管理会话等。下面是一个简单的 Shiro SessionManager 的配置示例: ```xml <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- session过期时间,单位:毫秒 --> <property name="globalSessionTimeout" value="1800000" /> <!-- 是否开启URL地址栏SessionID重写 --> <property name="sessionIdUrlRewritingEnabled" value="false" /> <!-- 是否开启删除无效的session对象 --> <property name="deleteInvalidSessions" value="true" /> <!-- 是否开启定时调度器检测失效的session对象 --> <property name="sessionValidationSchedulerEnabled" value="true" /> <!-- 定时清理失效session的时间间隔,单位:毫秒;默认1小时 --> <property name="sessionValidationInterval" value="3600000" /> <!-- 会话DAO --> <property name="sessionDAO" ref="redisSessionDAO" /> </bean> ``` 其,`DefaultWebSessionManager` 是 Shiro 默认提供的 Web 环境下的 SessionManager 实现类,我们可以通过配置它的属性来实现相关功能。具体的属性含义如下: - `globalSessionTimeout`:会话超时时间,单位为毫秒,默认为30分钟。 - `sessionIdUrlRewritingEnabled`:是否在URL地址栏重写Session ID,默认为false,建议不开启,因为这会让URL泄漏了Session ID,存在安全风险。 - `deleteInvalidSessions`:是否开启删除无效Session对象,默认为true,表示当Session超时或者被踢出时自动删除相应的Session对象。 - `sessionValidationSchedulerEnabled`:是否开启定时调度器检测失效的Session对象,默认为true,表示在应用启动时会启动一个定时任务,定期检测失效的Session并删除它们。 - `sessionValidationInterval`:定时清理失效Session的时间间隔,单位为毫秒,默认为1小时。 - `sessionDAO`:指定会话DAO,即会话的存储方式。这里我们使用 Redis 作为会话存储方式,所以指定了 `redisSessionDAO`。 需要注意的是,如果要使用 Redis 作为会话存储方式,还需要配置相应的 `redisSessionDAO`。具体的配置可以参考下面的示例: ```xml <bean id="redisSessionDAO" class="org.crazycake.shiro.RedisSessionDAO"> <!-- 配置RedisManager --> <property name="redisManager" ref="redisManager" /> <!-- session在Redis的过期时间,单位是秒 --> <property name="expire" value="1800" /> </bean> <bean id="redisManager" class="org.crazycake.shiro.RedisManager"> <!-- Redis服务器地址,格式为:host:port --> <property name="host" value="127.0.0.1:6379" /> <!-- Redis服务器连接超时时间,单位为毫秒,默认为2000ms --> <property name="timeout" value="2000" /> <!-- Redis服务器密码,如果没有设置可以不填 --> <property name="password" value="your_password" /> <!-- Redis数据库编号,默认为0 --> <property name="database" value="0" /> </bean> ``` 其,`RedisSessionDAO` 是一个实现了 Shiro `SessionDAO` 接口的 Redis Session 存储类。`RedisManager` 则是一个 Redis 连接管理类,用于管理与 Redis 服务器的连接。需要注意的是,上面的 `redisSessionDAO` 和 `redisManager` 需要通过 Spring 容器进行管理,所以需要在 Spring 配置文件进行相应的配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值