欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Shiro框架 Subject、SecurityManager、线程之间的关系

程序员文章站 2022-05-12 11:33:25
...

Subject与SecurityManager之间的关系

Subject对象的创建

Subject的创建是在过滤器中进行创建的,之后我会出过滤器相关的文章

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

createSubject(request, response); 中会根据request和response参数来进行创建。

创建的方式是采用构造者模式

public static class Builder extends Subject.Builder {

        public Builder(ServletRequest request, ServletResponse response) {
            this(SecurityUtils.getSecurityManager(), request, response);
        }
        public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
            super(securityManager);
            if (request == null) {
                throw new IllegalArgumentException("ServletRequest argument cannot be null.");
            }
            if (response == null) {
                throw new IllegalArgumentException("ServletResponse argument cannot be null.");
            }
            setRequest(request);
            setResponse(response);
        }
        @Override
        protected SubjectContext newSubjectContextInstance() {
            return new DefaultWebSubjectContext();
        }
        protected Builder setRequest(ServletRequest request) {
            if (request != null) {
                ((WebSubjectContext) getSubjectContext()).setServletRequest(request);
            }
            return this;
        }
        protected Builder setResponse(ServletResponse response) {
            if (response != null) {
                ((WebSubjectContext) getSubjectContext()).setServletResponse(response);
            }
            return this;
        }
        public WebSubject buildWebSubject() {
            Subject subject = super.buildSubject();
            if (!(subject instanceof WebSubject)) {
                String msg = "Subject implementation returned from the SecurityManager was not a " +
                        WebSubject.class.getName() + " implementation.  Please ensure a Web-enabled SecurityManager " +
                        "has been configured and made available to this builder.";
                throw new IllegalStateException(msg);
            }
            return (WebSubject) subject;
        }
    }

在Builder的构造方法中,调用父类构造方法,父类也叫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);
        }
       

主要完成的事情有:

  1. 生成DefaultWebSubjectContext对象,这个对象从类的结构上来看大致是一个实现了Map的功能
    Shiro框架 Subject、SecurityManager、线程之间的关系MapContext对象中使用了backingMap变量
    当作为整个对象返回时,采用不可变方式返回,使原有数据不受到污染
public Set<String> keySet() {
        return Collections.unmodifiableSet(backingMap.keySet());
    }

    public Collection<Object> values() {
        return Collections.unmodifiableCollection(backingMap.values());
    }

    public Set<Entry<String, Object>> entrySet() {
        return Collections.unmodifiableSet(backingMap.entrySet());
    }
  1. securityManager设置进入DefaultWebSubjectContext对象

最后在在类的Builder构造方法中,将requestresponse也设置进DefaultWebSubjectContext对象中
最后调用buildWebSubject方法来进行Subject的创建

public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }

创建的过程如下,里面是一些业务上的代码,博主还没有啃透彻,等我把大致流程弄懂,回来再细细研究

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

重点在这行Subject subject = doCreateSubject(context);来创建Subject对象

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

getSubjectFactory()中获取工厂对象,该工厂对象为我们配置文件中配置的CasSubjectFactory
Shiro框架 Subject、SecurityManager、线程之间的关系最后会调用父类DefaultWebSubjectFactory的方法来进行创建

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

securityManager对象会作为WebDelegatingSubject变量存储,Subject真正的实现是WebDelegatingSubject
当我们调用Subject.login,会执行到WebDelegatingSubject父类DelegatingSubject中,而securityManager会通过构造函数,传递到父类属性中
Shiro框架 Subject、SecurityManager、线程之间的关系

Subject、SecurityManager与线程之间的关系

当通过Shiro进行登录验证时,需要获取Subject对象,而在同一个线程中,每次通过SecurityUtils都是同一个对象。

Subject生成完之后谈到的问题是如何绑定到线程中

在如下代码中执行的绑定

subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
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对象,改类只是实现了Callable接口,没什么好说的

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

最最最后生成SubjectThreadState对象,这个对象很重要,主要负责将subject和securityManager通过ThreadContext对象绑定到当前线程中

当外层调用call方法时
Shiro框架 Subject、SecurityManager、线程之间的关系会调用SubjectThreadState的bind方法

Shiro框架 Subject、SecurityManager、线程之间的关系实际绑定时会调用ThreadContext的bind方法

public static void bind(Subject subject) {
        if (subject != null) {
            put(SUBJECT_KEY, subject);
        }
    }

最最最重要的是

private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

这个实现,能够保证在子线程中与主线程共享一个对象,可以参见我之后的文章,也会对这个类进行分析的

到此为止Subject与SecurityManager绑定与Subject与线程的绑定就讲完了