Shiro学习(三)——关于Subject的创建

前言

本文属于源码分析,只想了解Shiro使用的读者请略过。

Subject接口是Shiro中非常重要的接口,客户端通常会使用Subject的方法进行登录、登出、鉴权、角色判断等功能。在《Shiro学习(一)——Shiro配置与快速开始》中,我们就使用了其login接口。Subject的功能似乎非常强大,那么它到底是什么,为什么能做这么多事呢,值得我们分析一下它的来龙去脉。

 

Subject创建

在《Shiro学习(一)——Shiro配置与快速开始》为例,Subject通过SecurityUtils.getSubject()方法获取,如下:

Subject subject = SecurityUtils.getSubject();

深入到SecurityUtils的源码中看看:

public abstract class SecurityUtils {

//......

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

//......
}

ThreadContext是shiro封装的一个操作线程局部变量的抽象类。第6行通过getSubject()方法获取Subject。在测试的例子中,因为还没创建Subject,所以第7行判断为null,因此第8行创建subject。看看Subject.Builder()干了什么:

public interface Subject {

//......

    public static class Builder {

        private final SubjectContext subjectContext;

        private final SecurityManager securityManager;

//......
        public Builder() {
            this(SecurityUtils.getSecurityManager());
        }

        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            }
            this.securityManager = securityManager;
            this.subjectContext = newSubjectContextInstance();
            if (this.subjectContext == null) {
                throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                        "cannot be null.");
            }
            this.subjectContext.setSecurityManager(securityManager);
        }

        protected SubjectContext newSubjectContextInstance() {
            return new DefaultSubjectContext();
        }
//......
        public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }
    
	}
}

初始化Builder主要做两件事,一是设置SecurityManager,二是创建DefaultSubjectContext作为SubjectContext。接着看看(new Subject.Builder()).buildSubject()怎么创建Subject。它调用了33-35行,34行返回一个由securityManager创建的subejct。因此我们要跟踪securityManager的createSubject方法,前面都是创建subject的外围,接下来才是创建subject对象的核心:

public class DefaultSecurityManager extends SessionsSecurityManager {

//......

    protected SubjectContext copy(SubjectContext subjectContext) {
        return new DefaultSubjectContext(subjectContext);
    }

    public Subject createSubject(SubjectContext subjectContext) {
        SubjectContext context = copy(subjectContext);

        context = ensureSecurityManager(context);
        context = resolveSession(context);
        context = resolvePrincipals(context);

        Subject subject = doCreateSubject(context);
        save(subject);

        return subject;
    }

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

    protected SubjectContext ensureSecurityManager(SubjectContext context) {
        if (context.resolveSecurityManager() != null) {
            log.trace("Context already contains a SecurityManager instance.  Returning.");
            return context;
        }
        log.trace("No SecurityManager found in context.  Adding self reference.");
        context.setSecurityManager(this);
        return context;
    }

    protected SubjectContext resolveSession(SubjectContext context) {
        if (context.resolveSession() != null) {
            log.debug("Context already contains a session.  Returning.");
            return context;
        }
        try {

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

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

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

    protected SessionKey getSessionKey(SubjectContext context) {
        Serializable sessionId = context.getSessionId();
        if (sessionId != null) {
            return new DefaultSessionKey(sessionId);
        }
        return null;
    }

    protected SubjectContext resolvePrincipals(SubjectContext context) {

        PrincipalCollection principals = context.resolvePrincipals();

        if (isEmpty(principals)) {
            log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");

            principals = getRememberedIdentity(context);

            if (!isEmpty(principals)) {
                log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +
                        "for subject construction by the SubjectFactory.");
                context.setPrincipals(principals);
            } else {
                log.trace("No remembered identity found.  Returning original context.");
            }
        }

        return context;
    }

//......
}

创建subejct第一件工作是创建SubjectContext,就是subject上下文。第10行复制一份刚才Builder创建的SubjectContext。当然这种复制对subjectContext内部的Map中的对象是浅拷贝,这样使每一个复制出来的SubjectContext都包含了设置好的SecurityManager。12行确保SecurityManager已被设置到SubjectContext中。13行设置Session到SubjectContext中,37行判断SubjectContext是否已有Session,因为SubjectContext从Builder复制出来只包含了SecurityManager,所以是null,因此走43行。由于之前SubjectContext没有设置过Session,因此43行也是返回null。14行是处理Principal,因为之前创建的SubjectContext没有涉及Principal,因此14行是指也没有做任何操作。16行创建Subject,跳转到23行调用DefaultSubjectFactory的createSubject方法。跟踪DefaultSubjectFactory的createSubject方法:

public class DefaultSubjectFactory implements SubjectFactory {

//......

    public Subject createSubject(SubjectContext context) {
        SecurityManager securityManager = context.resolveSecurityManager();
        Session session = context.resolveSession();
        boolean sessionCreationEnabled = context.isSessionCreationEnabled();
        PrincipalCollection principals = context.resolvePrincipals();
        boolean authenticated = context.resolveAuthenticated();
        String host = context.resolveHost();

        return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
    }

//......
}

这里会把session、principals、authenticated、host、sessionCreationEnabled查出,并设置到DelegatingSubject对象中。因此Subject subject = SecurityUtils.getSubject();这个语句最终返回一个DelegatingSubject对象。对于本文的例子而言,session、principals、host均为null,sessionCreationEnabled为true,authenticated为false。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值