之前在研究Shiro 源码的过程中,发现Shiro 会对request、response、session 进行包装。 下面研究其包装过程以及原理。

  Session是通过包装了request, 重写了其获取Session 的方法。 然后重写了一套Shiro 自己的Session 管理机制(这个session 和 Servlet的HeepSession 没有关系), 只是对外暴露的时候封装成一个ShiroHttpSession 对象(该对象内部包含Shiro 的Session), 最终ShiroHttpSession 相关的操作都会交给Shiro的Session。 Shiro的session 实现了一套自己的生成ID、创建Session、删除、修改、获取所有的等方法; 并且也有定时任务去处理过期的session 等策略。 并且SHiro 提供了可扩展的抽象类,基于抽象类可以快速实现Session 存到Redis 或其他操作。

1. 使用ServletContainerSessionManager 走原来Servlet的一套机制

1. shiro 配置

    // 权限管理,配置主要是Realm的管理认证
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//         注意realm必须在设置完认证其之后设置, 或者在设置 authenticator 的时候直接设置realm。setRealms 方法会将realm 同时设置到 authenticator 认证器中
        securityManager.setRealms(Lists.newArrayList(new CustomRealm()));
        return securityManager;
    }

2. 增加测试Controller, 查看相关类型

    @GetMapping("/login2")
    public String login2(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);

        System.out.println("华丽的分割线1~~~~");

        HttpServletRequest request1 = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response1 = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        HttpSession session1 = request1.getSession(false);
        System.out.println(request1);
        System.out.println(response1);
        System.out.println(session1);
        System.out.println("华丽的分割线2~~~~");

        Subject subject = SecurityUtils.getSubject();
        AuthenticationToken generateToken = new UsernamePasswordToken("zs", "111222");
        subject.login(generateToken);

        return "success";
    }

3. shiro 配置该路径允许匿名访问

4. 测试

访问后日志如下:

org.apache.shiro.web.servlet.ShiroHttpServletRequest@7adc6ef1
org.apache.catalina.connector.ResponseFacade@4802cdcd
org.apache.catalina.session.StandardSessionFacade@1374f85e
华丽的分割线1~~~~
org.apache.shiro.web.servlet.ShiroHttpServletRequest@7adc6ef1
org.apache.catalina.connector.ResponseFacade@4802cdcd
org.apache.catalina.session.StandardSessionFacade@1374f85e
华丽的分割线2~~~~

  可以看到默认对Request 进行了包装,Response和sesson 仍然使用原来Servlet 中使用的对象。

5. 原理

1. org.apache.shiro.web.mgt.DefaultWebSecurityManager#DefaultWebSecurityManager() 构造如下:

    public DefaultWebSecurityManager() {
        super();
        DefaultWebSessionStorageEvaluator webEvalutator = new DefaultWebSessionStorageEvaluator();  
        ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(webEvalutator);
        this.sessionMode = HTTP_SESSION_MODE;
        setSubjectFactory(new DefaultWebSubjectFactory());
        setRememberMeManager(new CookieRememberMeManager());
        setSessionManager(new ServletContainerSessionManager());
        webEvalutator.setSessionManager(getSessionManager());
    }

可以看到默认的sessionManager 是 org.apache.shiro.web.session.mgt.ServletContainerSessionManager:

类图图像:

Shiro源码(六)-session 管理&前后端分离项目从请求头传递session信息

 源码如下:

public class ServletContainerSessionManager implements WebSessionManager {

    //TODO - complete JavaDoc

    //TODO - read session timeout value from web.xml

    public ServletContainerSessionManager() {
    }

    public Session start(SessionContext context) throws AuthorizationException {
        return createSession(context);
    }

    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;
    }

    private String getHost(SessionContext context) {
        String host = context.getHost();
        if (host == null) {
            ServletRequest request = WebUtils.getRequest(context);
            if (request != null) {
                host = request.getRemoteHost();
            }
        }
        return host;

    }

    /**
     * @since 1.0
     */
    protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
        if (!WebUtils.isHttp(sessionContext)) {
            String msg = "SessionContext must be an HTTP compatible implementation.";
            throw new IllegalArgumentException(msg);
        }

        HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);

        HttpSession httpSession = request.getSession();

        //SHIRO-240: DO NOT use the 'globalSessionTimeout' value here on the acquired session.
        //see: https://issues.apache.org/jira/browse/SHIRO-240

        String host = getHost(sessionContext);

        return createSession(httpSession, host);
    }

    protected Session createSession(HttpSession httpSession, String host) {
        return new HttpServletSession(httpSession, host);
    }

    /**
     * This implementation always delegates to the servlet container for sessions, so this method returns
     * {@code true} always.
     *
     * @return {@code true} always
     * @since 1.2
     */
    public boolean isServletContainerSessions() {
        return true;
    }
}

2. 代理代码查看:

org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal 是Shiro的过滤器执行的入口:

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        Throwable t = null;

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

(1) 包装Request的代码: org.apache.shiro.web.servlet.AbstractShiroFilter#prepareServletRequest

    protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletRequest toUse = request;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest http = (HttpServletRequest) request;
            toUse = wrapServletRequest(http);
        }
        return toUse;
    }

    protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
        return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
    }

    protected boolean isHttpSessions() {
        return getSecurityManager().isHttpSessionMode();
    }

1》org.apache.shiro.web.servlet.ShiroHttpServletRequest#ShiroHttpServletRequest 构造如下:

public class ShiroHttpServletRequest extends HttpServletRequestWrapper {

    public static final String COOKIE_SESSION_ID_SOURCE = "cookie";
    public static final String URL_SESSION_ID_SOURCE = "url";
    public static final String REFERENCED_SESSION_ID = ShiroHttpServletRequest.class.getName() + "_REQUESTED_SESSION_ID";
    public static final String REFERENCED_SESSION_ID_IS_VALID = ShiroHttpServletRequest.class.getName() + "_REQUESTED_SESSION_ID_VALID";
    public static final String REFERENCED_SESSION_IS_NEW = ShiroHttpServletRequest.class.getName() + "_REFERENCED_SESSION_IS_NEW";
    public static final String REFERENCED_SESSION_ID_SOURCE = ShiroHttpServletRequest.class.getName() + "REFERENCED_SESSION_ID_SOURCE";
    public static final String IDENTITY_REMOVED_KEY = ShiroHttpServletRequest.class.getName() + "_IDENTITY_REMOVED_KEY";
    public static final String SESSION_ID_URL_REWRITING_ENABLED = ShiroHttpServletRequest.class.getName() + "_SESSION_ID_URL_REWRITING_ENABLED";

    protected ServletContext servletContext = null;

    protected HttpSession session = null;
    protected boolean httpSessions = true;

    public ShiroHttpServletRequest(HttpServletRequest wrapped, ServletContext servletContext, boolean httpSessions) {
        super(wrapped);
        this.servletContext = servletContext;
        this.httpSessions = httpSessions;
    }

2》判断isHttpSessions 是否是 httpSession 的方法如下:

org.apache.shiro.web.mgt.DefaultWebSecurityManager#isHttpSessionMode:

    public boolean isHttpSessionMode() {
        SessionManager sessionManager = getSessionManager();
        return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions();
    }

  也就是判断是否是WebSessionManager, 然后调用 isServletContainerSessions 判断。 默认的ServletContainerSessionManager 返回true。 所以是HttpSessionMode。

(2) 包装Response 的代码 org.apache.shiro.web.servlet.AbstractShiroFilter#prepareServletResponse

    protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletResponse toUse = response;
        if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
            //the ShiroHttpServletResponse exists to support URL rewriting for session ids.  This is only needed if
            //using Shiro sessions (i.e. not simple HttpSession based sessions):
            toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
        }
        return toUse;
    }

  可以看到包装的条件是: !isHttpSessions() 并且request是 ShiroHttpServletRequest; 并且 response instanceof HttpServletResponse。 第一个条件不满足,所以不会进行包装。

3》 session 进行包装的条件:

  createSubject(request, response); 创建Subject, 会进行解析相关session。

(1) 调用到: org.apache.shiro.mgt.DefaultSecurityManager#resolveContextSession

    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
        SessionKey key = getSessionKey(context);
        if (key != null) {
            return getSession(key);
        }
        return null;
    }
  • 调用 org.apache.shiro.web.mgt.DefaultWebSecurityManager#getSessionKey 获取SessionKey
    protected SessionKey getSessionKey(SubjectContext context) {
        if (WebUtils.isWeb(context)) {
            Serializable sessionId = context.getSessionId();
            ServletRequest request = WebUtils.getRequest(context);
            ServletResponse response = WebUtils.getResponse(context);
            return new WebSessionKey(sessionId, request, response);
        } else {
            return super.getSessionKey(context);

        }
    }

  第一次获取到的sessionId 为空,返回一个WebSessionKey 对象, sessionId 为空。

  • 调用org.apache.shiro.mgt.SessionsSecurityManager#getSession 获取session
    public Session getSession(SessionKey key) throws SessionException {
        return this.sessionManager.getSession(key);
    }

继续调用调用到:org.apache.shiro.web.session.mgt.ServletContainerSessionManager#getSession

    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;
    }

    protected Session createSession(HttpSession httpSession, String host) {
        return new HttpServletSession(httpSession, host);
    }

继续调用到: org.apache.shiro.web.servlet.ShiroHttpServletRequest#getSession(boolean)

    public HttpSession getSession(boolean create) {

        HttpSession httpSession;

        if (isHttpSessions()) {
            httpSession = super.getSession(false);
            if (httpSession == null && create) {
                //Shiro 1.2: assert that creation is enabled (SHIRO-266):
                if (WebUtils._isSessionCreationEnabled(this)) {
                    httpSession = super.getSession(create);
                } else {
                    throw newNoSessionCreationException();
                }
            }
        } else {
            boolean existing = getSubject().getSession(false) != null;
            
            if (this.session == null || !existing) {
                Session shiroSession = getSubject().getSession(create);
                if (shiroSession != null) {
                    this.session = new ShiroHttpSession(shiroSession, this, this.servletContext);
                    if (!existing) {
                        setAttribute(REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
                    }
                } else if (this.session != null) {
                    this.session = null;
                }
            }
            httpSession = this.session;
        }

        return httpSession;
    }

  可以看到isHttpSessions 是true, 所以走上面不包装的代码逻辑。也就是所有的session 都走的原来session的一套机制。

2. 修改Sessionmanager为DefaultWebSessionManager走Shiro的机制

1. 修改配置

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//         注意realm必须在设置完认证其之后设置, 或者在设置 authenticator 的时候直接设置realm。setRealms 方法会将realm 同时设置到 authenticator 认证器中
        securityManager.setRealms(Lists.newArrayList(new CustomRealm()));
        securityManager.setSessionManager(new DefaultWebSessionManager());
        return securityManager;
    }

2. 测试

查看控制台如下:

org.apache.shiro.web.servlet.ShiroHttpServletRequest@675f5183
org.apache.shiro.web.servlet.ShiroHttpServletResponse@6998a318
org.apache.shiro.web.servlet.ShiroHttpSession@6da73af3
华丽的分割线1~~~~
org.apache.shiro.web.servlet.ShiroHttpServletRequest@675f5183
org.apache.shiro.web.servlet.ShiroHttpServletResponse@6998a318
org.apache.shiro.web.servlet.ShiroHttpSession@6da73af3
华丽的分割线2~~~~

  可以看到对request、response、session 都进行了包装。查看相关对象如下:

(1) request:

Shiro源码(六)-session 管理&前后端分离项目从请求头传递session信息

 (2) response:

Shiro源码(六)-session 管理&前后端分离项目从请求头传递session信息

(3) session:

Shiro源码(六)-session 管理&前后端分离项目从请求头传递session信息

4. 包装原理

1. org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal 在Filter 内部, Shiro 对request、response 进行了包装。

    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        Throwable t = null;

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

  prepareServletRequest 包装request; prepareServletResponse 包装response。  因为org.apache.shiro.web.session.mgt.DefaultWebSessionManager#isServletContainerSessions 返回是false, 所以取后是true。 那么满足包装的条件。

    protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletResponse toUse = response;
        if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
            //the ShiroHttpServletResponse exists to support URL rewriting for session ids.  This is only needed if
            //using Shiro sessions (i.e. not simple HttpSession based sessions):
            toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
        }
        return toUse;
    }

2. 进行后续调用  executeChain(request, response, chain); 用的都是包装后的request, response。 

3. 过滤器执行完后会进入Servlet 调用, 调用SpringMVC 的方法org.springframework.web.servlet.DispatcherServlet#doService 之前会先进入其父类 org.springframework.web.servlet.FrameworkServlet#processRequest:

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response);
        }
        catch (ServletException | IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            logResult(request, response, failureCause, asyncManager);
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

1》 调用 buildRequestAttributes(request, response, previousAttributes); 将Request、Response 维护起来, 这里的request和response 对象都是包装后的对象。

2》 org.springframework.web.servlet.FrameworkServlet#initContextHolders 将上面的对象保存到ThreadLocal 中, 也就是当前环境使用的request、response 以及传递到SpringMVC、Controller 的request、response 都是shiro 包装后的对象。

    private void initContextHolders(HttpServletRequest request,
            @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
    }

  RequestContextHolder 是Spring 记录请求上下文环境的对象,用的也是ThreadLocal 对象进行维护。

3. session管理

  修改为DefaultWebSessionManager  之后,实际就是将session 交给shiro 管理。 

1. 类图:

Shiro源码(六)-session 管理&前后端分离项目从请求头传递session信息

2. 重要类

1. org.apache.shiro.web.session.mgt.DefaultWebSessionManager#DefaultWebSessionManager 构造如下:

    public DefaultWebSessionManager() {
        Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
        cookie.setHttpOnly(true); //more secure, protects against XSS attacks
        this.sessionIdCookie = cookie;
        this.sessionIdCookieEnabled = true;
        this.sessionIdUrlRewritingEnabled = true;
    }

  sessionIdCookie 默认是读取名称为JSESSIONID 的cookie。

父类构造: org.apache.shiro.session.mgt.DefaultSessionManager#DefaultSessionManager  可以看到创建了一个 MemorySessionDAO

    public DefaultSessionManager() {
        this.deleteInvalidSessions = true;
        this.sessionFactory = new SimpleSessionFactory();
        this.sessionDAO = new MemorySessionDAO();
    }

涉及到的重要的类如下:

(1) org.apache.shiro.session.mgt.SimpleSessionFactory 源码如下: 简单创建一个session

package org.apache.shiro.session.mgt;

import org.apache.shiro.session.Session;

/**
 * {@code SessionFactory} implementation that generates {@link SimpleSession} instances.
 *
 * @since 1.0
 */
public class SimpleSessionFactory implements SessionFactory {

    /**
     * Creates a new {@link SimpleSession SimpleSession} instance retaining the context's
     * {@link SessionContext#getHost() host} if one can be found.
     *
     * @param initData the initialization data to be used during {@link Session} creation.
     * @return a new {@link SimpleSession SimpleSession} instance
     */
    public Session createSession(SessionContext initData) {
        if (initData != null) {
            String host = initData.getHost();
            if (host != null) {
                return new SimpleSession(host);
            }
        }
        return new SimpleSession();
    }
}

(2) org.apache.shiro.session.mgt.eis.SessionDAO 是一个接口,提供了session 的 增删改查

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;

import java.io.Serializable;
import java.util.Collection;


/**
 * Data Access Object design pattern specification to enable {@link Session} access to an
 * EIS (Enterprise Information System).  It provides your four typical CRUD methods:
 * {@link #create}, {@link #readSession(java.io.Serializable)}, {@link #update(org.apache.shiro.session.Session)},
 * and {@link #delete(org.apache.shiro.session.Session)}.
 * <p/>
 * The remaining {@link #getActiveSessions()} method exists as a support mechanism to pre-emptively orphaned sessions,
 * typically by {@link org.apache.shiro.session.mgt.ValidatingSessionManager ValidatingSessionManager}s), and should
 * be as efficient as possible, especially if there are thousands of active sessions.  Large scale/high performance
 * implementations will often return a subset of the total active sessions and perform validation a little more
 * frequently, rather than return a massive set and infrequently validate.
 *
 * @since 0.1
 */
public interface SessionDAO {

    /**
     * Inserts a new Session record into the underling EIS (e.g. Relational database, file system, persistent cache,
     * etc, depending on the DAO implementation).
     * <p/>
     * After this method is invoked, the {@link org.apache.shiro.session.Session#getId()}
     * method executed on the argument must return a valid session identifier.  That is, the following should
     * always be true:
     * <pre>
     * Serializable id = create( session );
     * id.equals( session.getId() ) == true</pre>
     * <p/>
     * Implementations are free to throw any exceptions that might occur due to
     * integrity violation constraints or other EIS related errors.
     *
     * @param session the {@link org.apache.shiro.session.Session} object to create in the EIS.
     * @return the EIS id (e.g. primary key) of the created {@code Session} object.
     */
    Serializable create(Session session);

    /**
     * Retrieves the session from the EIS uniquely identified by the specified
     * {@code sessionId}.
     *
     * @param sessionId the system-wide unique identifier of the Session object to retrieve from
     *                  the EIS.
     * @return the persisted session in the EIS identified by {@code sessionId}.
     * @throws UnknownSessionException if there is no EIS record for any session with the
     *                                 specified {@code sessionId}
     */
    Session readSession(Serializable sessionId) throws UnknownSessionException;

    /**
     * Updates (persists) data from a previously created Session instance in the EIS identified by
     * {@code {@link Session#getId() session.getId()}}.  This effectively propagates
     * the data in the argument to the EIS record previously saved.
     * <p/>
     * In addition to UnknownSessionException, implementations are free to throw any other
     * exceptions that might occur due to integrity violation constraints or other EIS related
     * errors.
     *
     * @param session the Session to update
     * @throws org.apache.shiro.session.UnknownSessionException
     *          if no existing EIS session record exists with the
     *          identifier of {@link Session#getId() session.getSessionId()}
     */
    void update(Session session) throws UnknownSessionException;

    /**
     * Deletes the associated EIS record of the specified {@code session}.  If there never
     * existed a session EIS record with the identifier of
     * {@link Session#getId() session.getId()}, then this method does nothing.
     *
     * @param session the session to delete.
     */
    void delete(Session session);

    /**
     * Returns all sessions in the EIS that are considered active, meaning all sessions that
     * haven't been stopped/expired.  This is primarily used to validate potential orphans.
     * <p/>
     * If there are no active sessions in the EIS, this method may return an empty collection or {@code null}.
     * <h4>Performance</h4>
     * This method should be as efficient as possible, especially in larger systems where there might be
     * thousands of active sessions.  Large scale/high performance
     * implementations will often return a subset of the total active sessions and perform validation a little more
     * frequently, rather than return a massive set and validate infrequently.  If efficient and possible, it would
     * make sense to return the oldest unstopped sessions available, ordered by
     * {@link org.apache.shiro.session.Session#getLastAccessTime() lastAccessTime}.
     * <h4>Smart Results</h4>
     * <em>Ideally</em> this method would only return active sessions that the EIS was certain should be invalided.
     * Typically that is any session that is not stopped and where its lastAccessTimestamp is older than the session
     * timeout.
     * <p/>
     * For example, if sessions were backed by a relational database or SQL-92 'query-able' enterprise cache, you might
     * return something similar to the results returned by this query (assuming
     * {@link org.apache.shiro.session.mgt.SimpleSession SimpleSession}s were being stored):
     * <pre>
     * select * from sessions s where s.lastAccessTimestamp < ? and s.stopTimestamp is null
     * </pre>
     * where the {@code ?} parameter is a date instance equal to 'now' minus the session timeout
     * (e.g. now - 30 minutes).
     *
     * @return a Collection of {@code Session}s that are considered active, or an
     *         empty collection or {@code null} if there are no active sessions.
     */
    Collection<Session> getActiveSessions();
}

org.apache.shiro.session.mgt.eis.AbstractSessionDAO 抽象dao:

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;

import java.io.Serializable;


/**
 * An abstract {@code SessionDAO} implementation that performs some sanity checks on session creation and reading and
 * allows for pluggable Session ID generation strategies if desired.  The {@code SessionDAO}
 * {@link SessionDAO#update update} and {@link SessionDAO#delete delete} methods are left to
 * subclasses.
 * <h3>Session ID Generation</h3>
 * This class also allows for plugging in a {@link SessionIdGenerator} for custom ID generation strategies.  This is
 * optional, as the default generator is probably sufficient for most cases.  Subclass implementations that do use a
 * generator (default or custom) will want to call the
 * {@link #generateSessionId(org.apache.shiro.session.Session)} method from within their {@link #doCreate}
 * implementations.
 * <p/>
 * Subclass implementations that rely on the EIS data store to generate the ID automatically (e.g. when the session
 * ID is also an auto-generated primary key), they can simply ignore the {@code SessionIdGenerator} concept
 * entirely and just return the data store's ID from the {@link #doCreate} implementation.
 *
 * @since 1.0
 */
public abstract class AbstractSessionDAO implements SessionDAO {

    /**
     * Optional SessionIdGenerator instance available to subclasses via the
     * {@link #generateSessionId(org.apache.shiro.session.Session)} method.
     */
    private SessionIdGenerator sessionIdGenerator;

    /**
     * Default no-arg constructor that defaults the {@link #setSessionIdGenerator sessionIdGenerator} to be a
     * {@link org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator}.
     */
    public AbstractSessionDAO() {
        this.sessionIdGenerator = new JavaUuidSessionIdGenerator();
    }

    /**
     * Returns the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
     * method.  Unless overridden by the {@link #setSessionIdGenerator(SessionIdGenerator)} method, the default instance
     * is a {@link JavaUuidSessionIdGenerator}.
     *
     * @return the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
     *         method.
     */
    public SessionIdGenerator getSessionIdGenerator() {
        return sessionIdGenerator;
    }

    /**
     * Sets the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
     * method.  Unless overridden by this method, the default instance ss a {@link JavaUuidSessionIdGenerator}.
     *
     * @param sessionIdGenerator the {@code SessionIdGenerator} to use in the
     *                           {@link #generateSessionId(org.apache.shiro.session.Session)} method.
     */
    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
        this.sessionIdGenerator = sessionIdGenerator;
    }

    /**
     * Generates a new ID to be applied to the specified {@code session} instance.  This method is usually called
     * from within a subclass's {@link #doCreate} implementation where they assign the returned id to the session
     * instance and then create a record with this ID in the EIS data store.
     * <p/>
     * Subclass implementations backed by EIS data stores that auto-generate IDs during record creation, such as
     * relational databases, don't need to use this method or the {@link #getSessionIdGenerator() sessionIdGenerator}
     * attribute - they can simply return the data store's generated ID from the {@link #doCreate} implementation
     * if desired.
     * <p/>
     * This implementation uses the {@link #setSessionIdGenerator configured} {@link SessionIdGenerator} to create
     * the ID.
     *
     * @param session the new session instance for which an ID will be generated and then assigned
     * @return the generated ID to assign
     */
    protected Serializable generateSessionId(Session session) {
        if (this.sessionIdGenerator == null) {
            String msg = "sessionIdGenerator attribute has not been configured.";
            throw new IllegalStateException(msg);
        }
        return this.sessionIdGenerator.generateId(session);
    }

    /**
     * Creates the session by delegating EIS creation to subclasses via the {@link #doCreate} method, and then
     * asserting that the returned sessionId is not null.
     *
     * @param session Session object to create in the EIS and associate with an ID.
     */
    public Serializable create(Session session) {
        Serializable sessionId = doCreate(session);
        verifySessionId(sessionId);
        return sessionId;
    }

    /**
     * Ensures the sessionId returned from the subclass implementation of {@link #doCreate} is not null and not
     * already in use.
     *
     * @param sessionId session id returned from the subclass implementation of {@link #doCreate}
     */
    private void verifySessionId(Serializable sessionId) {
        if (sessionId == null) {
            String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
            throw new IllegalStateException(msg);
        }
    }

    /**
     * Utility method available to subclasses that wish to
     * assign a generated session ID to the session instance directly.  This method is not used by the
     * {@code AbstractSessionDAO} implementation directly, but it is provided so subclasses don't
     * need to know the {@code Session} implementation if they don't need to.
     * <p/>
     * This default implementation casts the argument to a {@link SimpleSession}, Shiro's default EIS implementation.
     *
     * @param session   the session instance to which the sessionId will be applied
     * @param sessionId the id to assign to the specified session instance.
     */
    protected void assignSessionId(Session session, Serializable sessionId) {
        ((SimpleSession) session).setId(sessionId);
    }

    /**
     * Subclass hook to actually persist the given <tt>Session</tt> instance to the underlying EIS.
     *
     * @param session the Session instance to persist to the EIS.
     * @return the id of the session created in the EIS (i.e. this is almost always a primary key and should be the
     *         value returned from {@link org.apache.shiro.session.Session#getId() Session.getId()}.
     */
    protected abstract Serializable doCreate(Session session);

    /**
     * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt> by delegating to
     * the {@link #doReadSession(java.io.Serializable)} method.  If {@code null} is returned from that method, an
     * {@link UnknownSessionException} will be thrown.
     *
     * @param sessionId the id of the session to retrieve from the EIS.
     * @return the session identified by <tt>sessionId</tt> in the EIS.
     * @throws UnknownSessionException if the id specified does not correspond to any session in the EIS.
     */
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        Session s = doReadSession(sessionId);
        if (s == null) {
            throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
        }
        return s;
    }

    /**
     * Subclass implementation hook that retrieves the Session object from the underlying EIS or {@code null} if a
     * session with that ID could not be found.
     *
     * @param sessionId the id of the <tt>Session</tt> to retrieve.
     * @return the Session in the EIS identified by <tt>sessionId</tt> or {@code null} if a
     *         session with that ID could not be found.
     */
    protected abstract Session doReadSession(Serializable sessionId);

}
View Code

相关文章: