zoukankan      html  css  js  c++  java
  • Shiro源码分析之Subject和SecurityManager

    Subject

    毫无疑问,Subject是Shiro最重要的一个概念。

    “Subject”只是一个安全术语,意味着应用程序用户的特定于安全性的“视图”。Shiro Subject实例代表单个应用程序用户的安全状态和相关操作。

    创建

    初次创建是在AbstractShiroFilter#doFilterInternal方法中:

    final Subject subject = createSubject(request, response);

    protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
    }

    创建的时候传入安全管理器,Subject.Builder是这样操作的:

    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);
    }
    
    
    public Subject buildSubject() {
        return this.securityManager.createSubject(this.subjectContext);
    }

    这个安全管理器还是我们指定的那个DefaultWebSecurityManager,一路传过去的。这个subjectContext参数是一个DefaultSubjectContext,子接口中的Builder覆盖了父类的方法,实际赋予的是一个DefaultWebSubjectContext。

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

    subjectFactory是安全管理器DefaultWebSecurityManager中默认的DefaultWebSubjectFactory:

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

    最终的创建又回到:

    public class DefaultWebSubjectFactory extends DefaultSubjectFactory {
    
        public DefaultWebSubjectFactory() {
            super();
        }
    
        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);
        }
    
        //......
    }

    根据相关属性new出来一个WebDelegatingSubject。
    这里写图片描述

    shiro中很多都是这样的继承和组合关系:

    DefaultSecurityManager -> DefaultSubjectFactory -> DelegatingSubject

    DefaultWebSecurityManager -> DefaultWebSubjectFactory -> WebDelegatingSubject

    再回到创建的方法:

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

    这个execute方法是在DelegatingSubject中实现的:

    public <V> V execute(Callable<V> callable) throws ExecutionException {
        Callable<V> associated = associateWith(callable);
        try {
            return associated.call();
        } catch (Throwable t) {
            throw new ExecutionException(t);
        }
    }
    
    public <V> Callable<V> associateWith(Callable<V> callable) {
        return new SubjectCallable<V>(this, callable);
    }
    

    SubjectCallable首先构造了一个ThreadState:

    public SubjectCallable(Subject subject, Callable<V> delegate) {
        this(new SubjectThreadState(subject), delegate);
    }

    associated.call()调用ThreadState.bind():

    public V call() throws Exception {
        try {
            threadState.bind();
            return doCall(this.callable);
        } finally {
            threadState.restore();
        }
    }

    SubjectThreadState的bind方法:

    public void bind() {
        SecurityManager securityManager = this.securityManager;
        if ( securityManager == null ) {
            //try just in case the constructor didn't find one at the time:
            securityManager = ThreadContext.getSecurityManager();
        }
        this.originalResources = ThreadContext.getResources();
        ThreadContext.remove();
    
        ThreadContext.bind(this.subject);
        if (securityManager != null) {
            ThreadContext.bind(securityManager);
        }
    }

    这样就妥妥地把当前subject和线程绑定到了一起(还有securityManager)。

    在这里遇到一个问题,记录下:
    https://www.oschina.net/question/2275855_2273492

    其实这只是每次进入核心过滤器时默认为我们创建的一个Subject,当调用subject.login方法之后会再次创建一个Subject,后面登录部分会做详细介绍。

    获取

    Subject subject = SecurityUtils.getSubject();

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

    绑定是通过ThreadContext,获取当然也是从其取。

    public abstract class ThreadContext {
    
        private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
    
        public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
        public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
    
        private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
    
        private static Object getValue(Object key) {
            return resources.get().get(key);
        }
    
        public static Object get(Object key) {
    
            Object value = getValue(key);
            return value;
        }
    
        public static Subject getSubject() {
            return (Subject) get(SUBJECT_KEY);
        }
    
    }

    最终是到ThreadLocal中拿,不过这个ThreadLocal是 InheritableThreadLocalMap 类型的(继承自InheritableThreadLocal)。

    每个线程都有一个Map

    SecurityManager

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
        <property name="realm" ref="casRealm" />  
        <property name="sessionManager" ref="sessionManager" />
        <property name="cacheManager" ref="shiroCacheManager" />  
        <!-- <property name="rememberMeManager" ref="rememberMeManager" />  --> 
    </bean>
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.ServletContainerSessionManager"/>
    public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
    
        Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
    
        void logout(Subject subject);
    
        Subject createSubject(SubjectContext context);
    
    }

    安全管理器继承了Authenticator, Authorizer, SessionManager三个接口。自顶向下第一个抽象类是CachingSecurityManager,接着是RealmSecurityManager,后面是AuthenticatingSecurityManager,AuthorizingSecurityManager,SessionsSecurityManager。

    然后才是DefaultSecurityManager,DefaultWebSecurityManager。
    这里写图片描述

    由于是web项目,我们指定了DefaultWebSecurityManager,在构造器中会为我们设置相匹配的属性(都是和web相关的):

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

    SessionsSecurityManager持有一个sessionManager对象,对sessionManager接口的实现是转移到这个对象上来的:

    public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
    
        private SessionManager sessionManager;
    
        public SessionsSecurityManager() {
            super();
            this.sessionManager = new DefaultSessionManager();
            applyCacheManagerToSessionManager();
        }
    }

    AuthenticatingSecurityManager,AuthorizingSecurityManager类似,这两个抽象管理器在无参构造函数中创建了默认的对象:

    public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
    
        private Authorizer authorizer;
    
        public AuthorizingSecurityManager() {
            super();
            this.authorizer = new ModularRealmAuthorizer();
        }
    
    }
    
    public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
    
        private Authenticator authenticator;
    
        public AuthenticatingSecurityManager() {
            super();
            this.authenticator = new ModularRealmAuthenticator();
        }
    }
    

    所以这个安全管理器几乎承担了所有的操作,然后转移到具体的对象。它的层次结构非常清晰,职责分明。对Subject所有操作最终都会转移到SecurityManager。

  • 相关阅读:
    可方便扩展的JIRA Rest Web API的封装调用
    小诗一首
    jxse2.6在jdk8下,JxtaMulticastSocket存在的问题
    http://blogs.msdn.com/b/pranavwagh/archive/2007/03/03/word-2007-file-seems-to-be-deleted-when-you-open-and-save-it-using-dsoframer.aspx
    how to javafx hide background header of a tableview?
    Styling FX Buttons with CSS
    2d网络游戏的延迟补偿(Lag compensation with networked 2D games)
    Fast-paced Multiplayer
    jspace2d——A free 2d multiplayer space shooter
    JXSE and Equinox Tutorial, Part 2
  • 原文地址:https://www.cnblogs.com/lucare/p/8679133.html
Copyright © 2011-2022 走看看