Shiro原始碼分析(1)
簡介
- SecurityManager:安全管理器,Shiro最核心元件。Shiro通過SecurityManager來管理內部元件例項,並通過它來提供安全管理的各種服務。
- Authenticator:認證器,認證AuthenticationToken是否有效。
- Authorizer:授權器,處理角色和許可權。
- SessionManager:Session管理器,管理Session。
- Subject:當前操作主體,表示當前操作使用者。
- SubjectContext:Subject上下文資料物件。
- AuthenticationToken:認證的token資訊(使用者名稱、密碼等)。
- ThreadContext:執行緒上下文物件,負責繫結物件到當前執行緒。
在學習和使用Shiro過程中,我們都知道SecurityManager介面在Shiro中是最為核心的介面。我們就沿著這個介面進行分析。
下面的程式碼是SecurityManager介面的定義:
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
<span class="hljs-comment">/**
* 登入
*/</span>
<span class="hljs-function">Subject <span class="hljs-title">login</span><span class="hljs-params">(Subject subject, AuthenticationToken authenticationToken)</span> <span class="hljs-keyword">throws</span> AuthenticationException</span>;
<span class="hljs-comment">/**
* 登出
*/</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">logout</span><span class="hljs-params">(Subject subject)</span></span>;
<span class="hljs-comment">/**
* 建立Subject
*/</span>
<span class="hljs-function">Subject <span class="hljs-title">createSubject</span><span class="hljs-params">(SubjectContext context)</span></span>;
}
在SecurityManager 中定義了三個方法,分別是登入、登出和建立Subject。通常我們在使用的時候是這樣使用的。首先建立Subject物件,然後通過呼叫login方法傳入認證資訊token對登入進行認證。
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
subject.login(token);
SecurityUtils分析
在Shiro中提供了一個方便使用的工具類SecurityUtils,SecurityUtils核心功能是獲取SecurityManager以及Subject。這兩個介面是Shiro提供的外圍介面,供開發時使用。
在SecurityUtils使用static定義SecurityManager,也就是說SecurityManager物件在應用中是單一存在的。
private static SecurityManager securityManager;
1. 獲取SecurityManager
首先從ThreadContext中獲取,如果沒有,則從SecurityUtils屬性securityManager中獲取。一定要存在一個SecurityManager例項物件,否則拋異常。
public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
SecurityManager securityManager = ThreadContext.getSecurityManager();
if (securityManager == null) {
securityManager = SecurityUtils.securityManager;
}
if (securityManager == null) {
String msg = "No SecurityManager accessible to the calling code, either bound to the " +
ThreadContext.class.getName() + " or as a vm static singleton. This is an invalid application " +
"configuration.";
throw new UnavailableSecurityManagerException(msg);
}
return securityManager;
}
2. 獲取Subject
首先從ThreadContext中獲取,如果不存在,則建立新的Subject,再存放到ThreadContext中,以便下次可以獲取。
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
在上面的程式碼中重要是通過 Subject.Builder類提供的buildSubject()方法來建立Subject。在建立Subject時同時還建立了SubjectContext物件,也就是說Subject和SubjectContext是一一對應的。下面的程式碼是Subject.Builder類的構造方法。
public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
}
this.securityManager = securityManager;
// 建立了SubjectContext例項物件
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
}
this.subjectContext.setSecurityManager(securityManager);
}
而buildSubject()方法則實際上是呼叫SecurityManager介面中的createSubject(SubjectContext subjectContext)方法。
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
總結
本篇主要通過SecurityUtils.getSubject()對SecurityManager介面中的createSubject(SubjectContext subjectContext)方法進行了詳細的分析。另外兩個方法我們在分析Subject時做詳細分析。
另外,我們會發現SecurityManager繼承了 Authenticator, Authorizer, SessionManager三個介面,這樣才能實現SecurityManager提供安全管理的各種服務。在接下來的文章中會對Authenticator, Authorizer, SessionManager分別進行分析,這樣我們對SecurityManager基本上就掌握了。