前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >shiro中改造成restful无状态服务的DisabledSessionException问题分析与解决

shiro中改造成restful无状态服务的DisabledSessionException问题分析与解决

作者头像
山行AI
发布2019-12-02 20:03:38
2.3K0
发布2019-12-02 20:03:38
举报
文章被收录于专栏:山行AI山行AI

关于 shiro 改造部分,之前有写文章专门讲过流程,长话短说,直接进入主题。

改造部分

SecurityManager 的配置如下:

@Bean    public SecurityManager securityManager(OAuthRealm oAuthRealm) {        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        securityManager.setRealm(oAuthRealm);        securityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());        securityManager.setCacheManager(getEhCache());        //securityManager.setSessionManager(sessionManager);        // 自定义缓存实现 使用redis        //securityManager.setCacheManager(cacheManager());        return securityManager;    }

StatelessDefaultSubjectFactory 的代码为:

public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {    @Override    public Subject createSubject(SubjectContext context) {        //不创建session        context.setSessionCreationEnabled(false);        return super.createSubject(context);    }}

DisabledSessionException

运行后,在调用 subject.login(token)方法时报错,报错信息如下:

org.apache.shiro.subject.support.DisabledSessionException: 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 org.apache.shiro.subject.support.DisabledSessionException JavaDoc for more.

分析

这里不去描述太多,直接从源码一步步来看: org.apache.shiro.subject.support.DelegatingSubject#login:

 public void login(AuthenticationToken token) throws AuthenticationException {        clearRunAsIdentitiesInternal();        Subject subject = securityManager.login(this, token);
        PrincipalCollection principals;
        String host = null;
   ...........    }

这里我们主要关注下 securityManager.login(this, token)即 org.apache.shiro.mgt.DefaultSecurityManager#login:

 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 loggedIn = createSubject(token, info, subject);
        onSuccessfulLogin(token, info, loggedIn);
        return loggedIn;    }

进入 createSubject:

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

再看 createSubject(context):

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

到了这里,我们主要关注以下两点:

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

这里会调用 StatelessDefaultSubjectFactory 的 createSubject 方法,创建的是那个禁用 session 的 subject。

  • save(subject):
  protected void save(Subject subject) {        this.subjectDAO.save(subject);    }
org.apache.shiro.mgt.DefaultSubjectDAO#save:
  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 boolean isSessionStorageEnabled(Subject subject) {        return getSessionStorageEvaluator().isSessionStorageEnabled(subject);    }
      public SessionStorageEvaluator getSessionStorageEvaluator() {        return sessionStorageEvaluator;    }

    org.apache.shiro.mgt.DefaultSessionStorageEvaluator#isSessionStorageEnabled(org.apache.shiro.subject.Subject):     public boolean isSessionStorageEnabled(Subject subject) {        return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();    }

这里我索性把代码都放在一起,方便查看。可以看到默认用的是 DefaultSessionStorageEvaluator,它的 isSessionStorageEnabled 的结果为 true,从而会进入到 saveToSession(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);    }
  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);
        if (session == null) {            if (!isEmpty(currentPrincipals)) {                session = subject.getSession();                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);            }            // otherwise no session and no principals - nothing to save        } else {            PrincipalCollection existingPrincipals =                    (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (isEmpty(currentPrincipals)) {                if (!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            }        }    }

上面的代码比较长,我们主要关注下 session = subject.getSession();即 org.apache.shiro.subject.support.DelegatingSubject#getSession():

  public Session getSession() {        return getSession(true);    }
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());            SessionContext sessionContext = createSessionContext();            Session session = this.securityManager.start(sessionContext);            this.session = decorate(session);        }        return this.session;    }

到这里你应该是看到异常出现的地方了。这里由于时间关系,就不再具体去分析了。

解决办法

securityManager:

 @Bean    public SecurityManager securityManager(OAuthRealm oAuthRealm) {        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        securityManager.setRealm(oAuthRealm);        securityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());        securityManager.setCacheManager(getEhCache());        SubjectDAO subjectDAO = securityManager.getSubjectDAO();        if (subjectDAO instanceof DefaultSubjectDAO){            DefaultSubjectDAO defaultSubjectDAO = (DefaultSubjectDAO) subjectDAO;            SessionStorageEvaluator sessionStorageEvaluator = defaultSubjectDAO.getSessionStorageEvaluator();            if (sessionStorageEvaluator instanceof DefaultSessionStorageEvaluator){                DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = (DefaultSessionStorageEvaluator) sessionStorageEvaluator;                defaultSessionStorageEvaluator.setSessionStorageEnabled(false);            }        }        //securityManager.setSessionManager(sessionManager);        // 自定义缓存实现 使用redis        //securityManager.setCacheManager(cacheManager());        return securityManager;    }
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 改造部分
  • DisabledSessionException
  • 分析
  • 解决办法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档