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與線程的綁定就講完了