Shrio解析Session过程

第一次使用SecurityUtils.getSubject()来获取Subject时

public static Subject getSubject() {  
        Subject subject = ThreadContext.getSubject();  
        if (subject == null) {  
            subject = (new Subject.Builder()).buildSubject();  
            ThreadContext.bind(subject);  
        }  
        return subject;  
    }  

使用ThreadLocal模式来获取,若没有则创建一个并绑定到当前线程。此时创建使用的是Subject内部类Builder来创建的,Builder会创建一个SubjectContext接口的实例DefaultSubjectContext,最终会委托securityManager来根据SubjectContext信息来创建一个Subject,下面详细说下该过程,在DefaultSecurityManager的createSubject方法中:

public Subject createSubject(SubjectContext subjectContext) {
    //create a copy so we don't modify the argument's backing map:
    SubjectContext context = copy(subjectContext);

    //ensure that the context has a SecurityManager instance, and if not, add one:
    context = ensureSecurityManager(context);

    //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
    //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
    //process is often environment specific - better to shield the SF from these details:
    context = resolveSession(context);

    //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
    //if possible before handing off to the SubjectFactory:
    context = resolvePrincipals(context);

    Subject subject = doCreateSubject(context);

    //save this subject for future reference if necessary:
    //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
    //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
    //Added in 1.2:
    save(subject);

    return subject;
}

先看代码,发现有个一个方法是我们需要的及context = resolveSession(context),跟踪代码如下,具体分为1、创建Session;2、获取Session,本篇文章主要围绕这连个主题讲解
DefaultSecurityManager源码如下:

@SuppressWarnings({"unchecked"})
protected SubjectContext resolveSession(SubjectContext context) {
   // 1 创建Session
    if (context.resolveSession() != null) {
        log.debug("Context already contains a session.  Returning.");
        return context;
    }
    try {
        //Context couldn't resolve it directly, let's see if we can since we have direct access to 
        //the session manager:
        // 2 获取Sessioin
        Session session = resolveContextSession(context);
        if (session != null) {
            context.setSession(session);
        }
    } catch (InvalidSessionException e) {
        log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +
                "(session-less) Subject instance.", e);
    }
    return context;
}

创建Session

1.先尝试从Context中获取Session,如果没有则尝试从Context中获取Subject,然后从Subject中获取Session,如果获取到则直接返回context.。
DefaultSubjectContext源码:如下

public Session resolveSession() {
    Session session = getSession();//尝试从Context中获取Session
    if (session == null) {
        //try the Subject if it exists:
        Subject existingSubject = getSubject();// 从Context中获取Subject
        if (existingSubject != null) {
           // 1.1
            session = existingSubject.getSession(false);// 从Subject中获取Session
        }
    }
    return session;
}

1.1 从步骤1代码中我们发现获取Subject后调用getSession(false)方法(具体实现在DelegatingSubject)
getSession的参数为false,标示是否创建Session;如果Session为空,并且传入的参数为true则创建一个Session,而传入的参数为false,所以此处不会创建Session。从以下源码我们分析创建Session分三步,
①先创建SessionContext,
②然后根据SessionContext创建Session,
③接着会装饰Session。
接下来具体分析①②

public Session getSession(boolean create) {
    if (log.isTraceEnabled()) {
        log.trace("attempting to get session; create = " + create +
                "; session is null = " + (this.session == null) +
                "; session has id = " + (this.session != null && session.getId() != null));
    }

    if (this.session == null && create) {

        //added in 1.2:
        if (!isSessionCreationEnabled()) {
            String msg = "Session creation has been disabled for the current subject.  This exception indicates " +
                    "that there is either a programming error (using a session when it should never be " +
                    "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
                    "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
                    "for more.";
            throw new DisabledSessionException(msg);
        }

        log.trace("Starting session for host {}", getHost());
        // 1.1.1
        SessionContext sessionContext = createSessionContext();// 创建SessionContext
        // 1.1.2
        Session session = this.securityManager.start(sessionContext);// 然后根据SessionContext创建Session
        this.session = decorate(session);// 接着会装饰Session
    }
    return this.session;
}

1.1.1 我们先看SessionContext的主要内容,下面是SessionContext类图信息及具体操作,无非就是设置获取Host及SessionId
在这里插入图片描述
SessionContext 类

    public interface SessionContext extends Map<String, Object> {
        void setHost(String host);
        String getHost();
        Serializable getSessionId();
        void setSessionId(Serializable sessionId);
    }

看完SessionContext类我们从新回到上边① 及createSessionContext()方法,具体源码如下,我们发现吧ServletRequest、ServletResponse和host封装到SessionContext中。

   @Override
    protected SessionContext createSessionContext() {
        WebSessionContext wsc = new DefaultWebSessionContext();
        String host = getHost();
        if (StringUtils.hasText(host)) {
            wsc.setHost(host);
        }
        wsc.setServletRequest(this.servletRequest);
        wsc.setServletResponse(this.servletResponse);
        return wsc;
    }

1.1.2 接着会调用this.securityManager.start(sessionContext),交由securityManager管理创建Session任务,具体代码在AbstractNativeSessionManager类中实现

public Session start(SessionContext context) {
    // 1.1.2.1 
    Session session = createSession(context);// 创建Session,具体看1.1.2.1
    // 1.1.2.2
    applyGlobalSessionTimeout(session);// 设置全局超时时间,具体看1.1.2.2
    onStart(session, context);// 判断是否是web程序,是则存储Cookie
    notifyStart(session);// 处理实现SessionListener
    //Don't expose the EIS-tier Session object to the client-tier:
    return createExposedSession(session, context);
}

1.1.2.1 创建Session,主要做了判断Session是否有效,创建Session,

protected Session createSession(SessionContext context) throws AuthorizationException {
    enableSessionValidationIfNecessary();// 如果启动定时任务,检查session的有效性
    return doCreateSession(context);// 创建Session
}
protected Session doCreateSession(SessionContext context) {
    Session s = newSessionInstance(context);// 创建一个Session实例,将SessionContext给Session,此时SessionId还没有创建
    if (log.isTraceEnabled()) {
        log.trace("Creating session for host {}", s.getHost());
    }
    create(s);// 创建Session,在这个方法中,会交个SessionDAO来处理,①①创建SessionId、②②存储Session信息
    return s;
}
protected Session newSessionInstance(SessionContext context) {
    return getSessionFactory().createSession(context);
}
protected void create(Session session) {
    if (log.isDebugEnabled()) {
        log.debug("Creating new EIS record for new session instance [" + session + "]");
    }
    // 具体操作交由sessionDAO实现
    sessionDAO.create(session);
}

①①从上述代码中我们发现创建Session交由SessionFactory管理,查看SimpleSessionFactory源码,利用SimpleSession创建Session对象

 public Session createSession(SessionContext initData) {
    if (initData != null) {
        String host = initData.getHost();
        if (host != null) {
            return new SimpleSession(host);
        }
    }
    return new SimpleSession();
}

②②session的具体操作在SessionDAO中,发现会先创建SessionId,如果支持缓存,则将Session缓存起来

public Serializable create(Session session) {
    Serializable sessionId = super.create(session);// 创建SessionId
    cache(session, sessionId);// 如果支持缓存则存储Session
    return sessionId;
}

1.1.2.2 applyGlobalSessionTimeout(session)设置全局超时时间,具体实现在AbstractNativeSessionManager类中

protected void applyGlobalSessionTimeout(Session session) {
    session.setTimeout(getGlobalSessionTimeout());
    onChange(session);
}
protected void onChange(Session session) {
    sessionDAO.update(session);
}

分析了这么多我们回到步骤1发现session = existingSubject.getSession(false);getSession传入的参数为false也就是说此时不会创建Session,具体创建哪Session是在什么时候创建的呢??????????????

回到最开始public Subject createSubject(SubjectContext subjectContext) 方法,假设context = resolveSession(context)已经执行完了,其实只实行了创建Session的过程,而且还没有给我们创建。

接着调用Subject subject = doCreateSubject(context);方法,代码如下,通过SubjectFactory工厂接口来创建Subject的,而DefaultSecurityManager默认使用的SubjectFactory是DefaultWebSubjectFactory;

 protected Subject doCreateSubject(SubjectContext context) {
    return getSubjectFactory().createSubject(context);
}

继续看createSubject(context)方法,仍然是将SubjectContext一些属性给WebSubContext对象,创建完成后保存

public Subject createSubject(SubjectContext context) {
    if (!(context instanceof WebSubjectContext)) {
        return super.createSubject(context);
    }
    WebSubjectContext wsc = (WebSubjectContext) context;
    SecurityManager securityManager = wsc.resolveSecurityManager();
    Session session = wsc.resolveSession();
    boolean sessionEnabled = wsc.isSessionCreationEnabled();
    PrincipalCollection principals = wsc.resolvePrincipals();
    boolean authenticated = wsc.resolveAuthenticated();
    String host = wsc.resolveHost();
    ServletRequest request = wsc.resolveServletRequest();
    ServletResponse response = wsc.resolveServletResponse();

    return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
            request, response, securityManager);
}

重新回到最开始public Subject createSubject(SubjectContext subjectContext) 方法,Subject subject = doCreateSubject(context);也执行完了,接着会调用save(subject);方法,我们发现在save方法中利用subjectDAO去调用save方法

 protected void save(Subject subject) {
    this.subjectDAO.save(subject);
}

默认使用DefaultSubjectDAO去操作save,首先判断isSessionStorageEnabled是否要存储Subject的session,如果要存储接着会调用saveToSession方法,

 public Subject save(Subject subject) {
    if (isSessionStorageEnabled(subject)) {
        saveToSession(subject);
    } else {
        log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
                "authentication state are expected to be initialized on every request or invocation.", subject);
    }

    return subject;
}
protected void saveToSession(Subject subject) {
    //performs merge logic, only updating the Subject's session if it does not match the current state:
    mergePrincipals(subject);
    mergeAuthenticationState(subject);
}

下面是具体存储过程,在mergePrincipals中我们关心的重点来了,其中有一个 Session session = subject.getSession(false);先去获取Session如果Session为null,则会调用session = subject.getSession();则默认调用subject.getSession(true);则会创建Session接着会走步骤1的流程

 protected void mergePrincipals(Subject subject) {
    //merge PrincipalCollection state:

    PrincipalCollection currentPrincipals = null;

    //SHIRO-380: added if/else block - need to retain original (source) principals
    //This technique (reflection) is only temporary - a proper long term solution needs to be found,
    //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible
    //
    //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +
    if (subject.isRunAs() && subject instanceof DelegatingSubject) {
        try {
            Field field = DelegatingSubject.class.getDeclaredField("principals");
            field.setAccessible(true);
            currentPrincipals = (PrincipalCollection)field.get(subject);
        } catch (Exception e) {
            throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);
        }
    }
    if (currentPrincipals == null || currentPrincipals.isEmpty()) {
        currentPrincipals = subject.getPrincipals();
    }

    Session session = subject.getSession(false);// 如果有Session则获取,否则返回null

    if (session == null) {
        if (!CollectionUtils.isEmpty(currentPrincipals)) {
            session = subject.getSession();// 如果有Session则获取,没有Session创建
            session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);// 获取Session,会走步骤2,具体请看下边分析
        }
        //otherwise no session and no principals - nothing to save
    } else {
        PrincipalCollection existingPrincipals =
                (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);

        if (CollectionUtils.isEmpty(currentPrincipals)) {
            if (!CollectionUtils.isEmpty(existingPrincipals)) {
                session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            }
            //otherwise both are null or empty - no need to update the session
        } else {
            if (!currentPrincipals.equals(existingPrincipals)) {
                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
            }
            //otherwise they're the same - no need to update the session
        }
    }
}


但是我们发现,在这段代码中,在第一次创建Subject时,此时的凭证为Null,也就是不会执行session = subject.getSession();那什么时候会创建Session呢?
Session session = subject.getSession(false);
    if (session == null) {
        if (!CollectionUtils.isEmpty(currentPrincipals)) {
            session = subject.getSession();
            session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
        }
        //otherwise no session and no principals - nothing to save
    } else {

跟踪代码发现当我们登录调用Subject.login()的时候,此时通过会有凭证,之后会创建Session,跟踪login代码,在DefaultSecurityManager来中会调用createSubject(token, info, subject),接着调用

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
    AuthenticationInfo info;
    try {
        info = authenticate(token);// 认证成功
    } catch (AuthenticationException ae) {
        try {
            onFailedLogin(token, ae, subject);
        } catch (Exception e) {
            if (log.isInfoEnabled()) {
                log.info("onFailedLogin method threw an " +
                        "exception.  Logging and propagating original AuthenticationException.", e);
            }
        }
        throw ae; //propagate
    }
    // 创建Subject
    Subject loggedIn = createSubject(token, info, subject);

    onSuccessfulLogin(token, info, loggedIn);

    return loggedIn;
}
 protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
    SubjectContext context = createSubjectContext();
    context.setAuthenticated(true);
    context.setAuthenticationToken(token);
    context.setAuthenticationInfo(info);
    if (existing != null) {
        context.setSubject(existing);
    }
    return createSubject(context);// 我们熟悉的方法
}

获取Session

2.如果从Context中没有取到Session则,尝试根据SessionId获取Session
DefaultSubjectContext源码:如下

protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
    // 2.1
    SessionKey key = getSessionKey(context);
    if (key != null) {
        // 2.2
        return getSession(key);
    }
    return null;
}

2.1 getSessionKey(context)具体过程在DefaultWebSecurityManager类中,源码如下,我们发现如果是Web程序,始终走第一个会返回WebSessionKey对象。所以在步骤2中key != null为true,会执行getSession(key)方法。

@Override
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);

    }
}

2.2 getSession(key)具体执行过程在SessionsSecurityManager类中,源码如下,交由sessionManager处理

public Session getSession(SessionKey key) throws SessionException {
    // 2.2.1
    return this.sessionManager.getSession(key);
}

2.2.1 从2.2步骤我们看出此时获取Session交给了sessionManager(具体执行过程在AbstractNativeSessionManager类中),没有做实质性的操作,接着会调用doGetSession(key)方法

public Session getSession(SessionKey key) throws SessionException {
    Session session = lookupSession(key);
    return session != null ? createExposedSession(session, key) : null;
}
private Session lookupSession(SessionKey key) throws SessionException {
    if (key == null) {
        throw new NullPointerException("SessionKey argument cannot be null.");
    }
    // 2.2.2
    return doGetSession(key);
}

2.2.2 doGetSession(key)具体实现在AbstractValidatingSessionManager类中,首先会调用任务调度器验证Session的有效性,然后接着调用retrieveSession(key)方法

@Override
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
    enableSessionValidationIfNecessary();

    log.trace("Attempting to retrieve session with key {}", key);
    // 2.2.3
    Session s = retrieveSession(key);
    if (s != null) {
        validate(s, key);
    }
    return s;
}

2.2.3 retrieveSession(key)具体实现在DefaultSessionManager类中,调用getSessionId(sessionKey)方法,sessionKey封装了Request和Response对象,此时我们会获取请求携带的Cookie信息,如果有SessionId则获取,如果sessionId不为空调用retrieveSessionFromDataSource(sessionId)方法,利用SessionDAO获取Session信息,如果有sessionId却没有取到session则抛出异常。

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 Session retrieveSessionFromDataSource(Serializable sessionId) throws     UnknownSessionException {
    return sessionDAO.readSession(sessionId);
}

总结:

一共有两次会进入createSubject(SubjectContext subjectContext)方法,

第一次是拦截器

OncePerRequestFilter.doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

----->>>>

AbstractShiroFilter.oFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)

----->>>>

AbstractShiroFilter.createSubject(ServletRequest request, ServletResponse response)

----->>>>

WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject

----->>>>

DefaultSecurityManager.createSubject(this.subjectContext);

第二次进入

DelegatingSubject.login(token)

----->>>>

DefaultWebSecurityManager.login(this, token)// 具体Session创建

----->>>>

DefaultSecurityManager.createSubject(token, info, subject)

----->>>>

DefaultSecurityManager.createSubject(this.subjectContext);

展开阅读全文

没有更多推荐了,返回首页