Shiro框架 Subject、SecurityManager、线程之间的关系
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);
}
主要完成的事情有:
- 生成
DefaultWebSubjectContext
对象,这个对象从类的结构上来看大致是一个实现了Map的功能
在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());
}
- 将
securityManager
设置进入DefaultWebSubjectContext
对象
最后在在类的Builder
构造方法中,将request
和response
也设置进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
最后会调用父类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会通过构造函数,传递到父类属性中
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方法时
会调用SubjectThreadState的bind方法
实际绑定时会调用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与线程的绑定就讲完了
上一篇: Enum使用场景及技巧
下一篇: ENUM使用与详解