Shiro使用和原始碼分析---5
getSubject分析
上一章看完了DefaultWebSecurityManager的建構函式,首先來分析getSubject函式。getSubject定義在AccessControlFilter中。
getSubject
protected Subject getSubject(ServletRequest request, ServletResponse response) {
return SecurityUtils.getSubject();
}
再看SecurityUtils的getSubject函式,
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
ThreadContext相當於一個執行緒安全的類,ThreadContext的getSubject函式定義如下
public static Subject getSubject() {
return (Subject) get(SUBJECT_KEY);
}
這裡的SUBJECT_KEY=ThreadContext.class.getName() + “_SUBJECT_KEY”;,其get函式定義在ThreadContext中,如下
public static Object get(Object key) {
if (log.isTraceEnabled()) {
String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
Object value = getValue(key);
if ((value != null) && log.isTraceEnabled()) {
String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
return value;
}
getValue函式定義在ThreadContext中,如下
private static Object getValue(Object key) {
Map<Object, Object> perThreadResources = resources.get();
return perThreadResources != null ? perThreadResources.get(key) : null;
}
這裡假設是第一次訪問該Subject,返回null。回到getSubject函式,這裡就需要呼叫buildSubject函式,首先看Builder的建構函式,定義在Subject介面中,如下
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);
}
該建構函式就是簡單的呼叫了newSubjectContextInstance,定義在Subject介面中,如下
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
DefaultSubjectContext的建構函式沒有什麼內容。回到getSubject函式中,接下來看buildSubject,如下
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
這裡的securityManager就是前面構造的DefaultWebSecurityManager啦,subjectContext=DefaultSubjectContext。
createSubject定義在DefaultSecurityManager中,
public Subject createSubject(SubjectContext subjectContext) {
SubjectContext context = copy(subjectContext);
context = ensureSecurityManager(context);
context = resolveSession(context);
context = resolvePrincipals(context);
Subject subject = doCreateSubject(context);
save(subject);
return subject;
}
下面一一分析這些函式。
copy
protected SubjectContext copy(SubjectContext subjectContext) {
return new DefaultSubjectContext(subjectContext);
}
DefaultSubjectContext的建構函式就是簡單的將SubjectContext 賦值。
ensureSecurityManager
ensureSecurityManager定義在DefaultSecurityManager中,
protected SubjectContext ensureSecurityManager(SubjectContext context) {
if (context.resolveSecurityManager() != null) {
log.trace("Context already contains a SecurityManager instance. Returning.");
return context;
}
log.trace("No SecurityManager found in context. Adding self reference.");
context.setSecurityManager(this);
return context;
}
這裡就是檢視SubjectContext是否有SecurityManager,如果沒有就進行設定。該context就是剛剛構造的DefaultSubjectContext,它的resolveSecurityManager如下
public SecurityManager resolveSecurityManager() {
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
if (log.isDebugEnabled()) {
log.debug("No SecurityManager available in subject context map. " +
"Falling back to SecurityUtils.getSecurityManager() lookup.");
}
try {
securityManager = SecurityUtils.getSecurityManager();
} catch (UnavailableSecurityManagerException e) {
if (log.isDebugEnabled()) {
log.debug("No SecurityManager available via SecurityUtils. Heuristics exhausted.", e);
}
}
}
return securityManager;
}
這個函式首先從該SubjectContext裡取出SecurityManager,如果沒有,就呼叫SecurityUtils的getSecurityManager從全域性中取。顯然這個函式再第一次呼叫的時候是找不到SecurityManager的,因此要返回到ensureSecurityManager進行設定。這樣,執行完ensureSecurityManager後,SecurityManager和SubjectContext就關聯起來了。
resolveSession
resolveSession定義在DefaultSecurityManager中,
protected SubjectContext resolveSession(SubjectContext context) {
if (context.resolveSession() != null) {
log.debug("Context already contains a session. Returning.");
return context;
}
try {
Session session = resolveContextSession(context);
if (session != null) {
context.setSession(session);
}
} catch (InvalidSessionException e) {
log.debug("Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous " +
"(session-less) Subject instance.", e);
}
return context;
}
這個函式和上面的ensureSecurityManager類似,首先呼叫resolveSession
resolveSession定義在DefaultSubjectContext中,
public Session resolveSession() {
Session session = getSession();
if (session == null) {
Subject existingSubject = getSubject();
if (existingSubject != null) {
session = existingSubject.getSession(false);
}
}
return session;
}
這個函式首先從該SubjectContext中獲取Session,如果沒有,就獲取Subject,再從Subject中獲取Session,如果找到則返回,否則返回null。因為是第一次呼叫該函式,所以肯定為null,所以回到resolveSession函式中,接下來進入resolveContextSession函式,構造一個Session,並和該SubjectContext相關聯。
resolveContextSession定義在DefaultSecurityManager中,
protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
SessionKey key = getSessionKey(context);
if (key != null) {
return getSession(key);
}
return null;
}
protected SessionKey getSessionKey(SubjectContext context) {
Serializable sessionId = context.getSessionId();
if (sessionId != null) {
return new DefaultSessionKey(sessionId);
}
return null;
}
這裡首先獲取sessionId ,這裡第一次呼叫也為null,因此直接返回null。因此resolveSession啥也沒幹。
resolvePrincipals
resolvePrincipals定義在DefaultSecurityManager中,
protected SubjectContext resolvePrincipals(SubjectContext context) {
PrincipalCollection principals = context.resolvePrincipals();
if (CollectionUtils.isEmpty(principals)) {
log.trace("No identity (PrincipalCollection) found in the context. Looking for a remembered identity.");
principals = getRememberedIdentity(context);
if (!CollectionUtils.isEmpty(principals)) {
log.debug("Found remembered PrincipalCollection. Adding to the context to be used " +
"for subject construction by the SubjectFactory.");
context.setPrincipals(principals);
} else {
log.trace("No remembered identity found. Returning original context.");
}
}
return context;
}
首先通過resolvePrincipals嘗試獲取PrincipalCollection,resolvePrincipals定義在DefaultSubjectContext中,
public PrincipalCollection resolvePrincipals() {
PrincipalCollection principals = getPrincipals();
if (CollectionUtils.isEmpty(principals)) {
AuthenticationInfo info = getAuthenticationInfo();
if (info != null) {
principals = info.getPrincipals();
}
}
if (CollectionUtils.isEmpty(principals)) {
Subject subject = getSubject();
if (subject != null) {
principals = subject.getPrincipals();
}
}
if (CollectionUtils.isEmpty(principals)) {
Session session = resolveSession();
if (session != null) {
principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
}
}
return principals;
}
該程式碼依次嘗試從SubjectContext、AuthenticationInfo、Subject和Session中獲取PrincipalCollection。這裡顯然為null,因此返回到resolvePrincipals中,通過getRememberedIdentity獲取。
getRememberedIdentity定義在DefaultSecurityManager中,
protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext) {
RememberMeManager rmm = getRememberMeManager();
if (rmm != null) {
try {
return rmm.getRememberedPrincipals(subjectContext);
} catch (Exception e) {
if (log.isWarnEnabled()) {
String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
"] threw an exception during getRememberedPrincipals().";
log.warn(msg, e);
}
}
}
return null;
}
getRememberMeManager首先從DefaultWebSecurityManager中獲取RememberMeManager,在前面分析的DefaultWebSecurityManager建構函式中可知,getRememberMeManager將會返回CookieRememberMeManager,因此下面看getRememberedPrincipals函式,getRememberedPrincipals定義在CookieRememberMeManager的父類AbstractRememberMeManager中,
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
PrincipalCollection principals = null;
try {
byte[] bytes = getRememberedSerializedIdentity(subjectContext);
if (bytes != null && bytes.length > 0) {
principals = convertBytesToPrincipals(bytes, subjectContext);
}
} catch (RuntimeException re) {
principals = onRememberedPrincipalFailure(re, subjectContext);
}
return principals;
}
getRememberedSerializedIdentity定義在CookieRememberMeManager中,
protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
if (!WebUtils.isHttp(subjectContext)) {
if (log.isDebugEnabled()) {
String msg = "SubjectContext argument is not an HTTP-aware instance. This is required to obtain a " +
"servlet request and response in order to retrieve the rememberMe cookie. Returning " +
"immediately and ignoring rememberMe operation.";
log.debug(msg);
}
return null;
}
WebSubjectContext wsc = (WebSubjectContext) subjectContext;
if (isIdentityRemoved(wsc)) {
return null;
}
HttpServletRequest request = WebUtils.getHttpRequest(wsc);
HttpServletResponse response = WebUtils.getHttpResponse(wsc);
String base64 = getCookie().readValue(request, response);
if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;
if (base64 != null) {
base64 = ensurePadding(base64);
if (log.isTraceEnabled()) {
log.trace("Acquired Base64 encoded identity [" + base64 + "]");
}
byte[] decoded = Base64.decode(base64);
if (log.isTraceEnabled()) {
log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
}
return decoded;
} else {
return null;
}
}
這裡傳入的引數並不是WebSubjectContext,因此會在第一個if內直接返回。因此層層往上返回到resolvePrincipals,最後啥也沒做。
doCreateSubject
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
getSubjectFactory()返回前面構造的DefaultWebSubjectFactory,因此createSubject定義在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);
}
因為這時context並不是WebSubjectContext,因此直接呼叫父類DefaultSubjectFactory的createSubject函式,
public Subject createSubject(SubjectContext context) {
SecurityManager securityManager = context.resolveSecurityManager();
Session session = context.resolveSession();
boolean sessionCreationEnabled = context.isSessionCreationEnabled();
PrincipalCollection principals = context.resolvePrincipals();
boolean authenticated = context.resolveAuthenticated();
String host = context.resolveHost();
return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
}
這裡大部分引數都為null,然後直接呼叫了DelegatingSubject的建構函式。因此,回到doCreateSubject函式,這裡就是構造了一個DelegatingSubject函式。
save
protected void save(Subject subject) {
this.subjectDAO.save(subject);
}
這裡的subjectDAO是在前面建構函式中構造的DefaultSubjectDAO,因此看DefaultSubjectDAO的save函式,
public Subject save(Subject subject) {
if (isSessionStorageEnabled(subject)) {
saveToSession(subject);
} else {
log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
"authentication state are expected to be initialized on every request or invocation.", subject);
}
return subject;
}
isSessionStorageEnabled也定義在DefaultSubjectDAO中,
protected boolean isSessionStorageEnabled(Subject subject) {
return getSessionStorageEvaluator().isSessionStorageEnabled(subject);
}
getSessionStorageEvaluator返回在DefaultSubjectDAO的建構函式中構造的DefaultSessionStorageEvaluator,因此接著看它的isSessionStorageEnabled函式,
public boolean isSessionStorageEnabled(Subject subject) {
return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();
}
這裡isSessionStorageEnabled預設的返回true,因此回到save中,接著看saveToSession,
protected void saveToSession(Subject subject) {
mergePrincipals(subject);
mergeAuthenticationState(subject);
}
該函式就是將Subject的狀態存在Session中,首先看mergePrincipals函式,
protected void mergePrincipals(Subject subject) {
PrincipalCollection currentPrincipals = null;
if (subject.isRunAs() && subject instanceof DelegatingSubject) {
try {
Field field = DelegatingSubject.class.getDeclaredField("principals");
field.setAccessible(true);
currentPrincipals = (PrincipalCollection)field.get(subject);
} catch (Exception e) {
throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);
}
}
if (currentPrincipals == null || currentPrincipals.isEmpty()) {
currentPrincipals = subject.getPrincipals();
}
Session session = subject.getSession(false);
if (session == null) {
if (!CollectionUtils.isEmpty(currentPrincipals)) {
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
} else {
PrincipalCollection existingPrincipals =
(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (CollectionUtils.isEmpty(currentPrincipals)) {
if (!CollectionUtils.isEmpty(existingPrincipals)) {
session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
}
} else {
if (!currentPrincipals.equals(existingPrincipals)) {
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
}
}
}
首先,在前面doCreateSubject中構造的確實是一個DelegatingSubject,這裡isRunAs會返回false,至於為什麼,這裡就不分析了。下面會進入第二個if,currentPrincipals會被賦值為null(檢視之前構造的DelegatingSubject)。下面看Subject的getSession函式,
public Session getSession(boolean create) {
if (log.isTraceEnabled()) {
log.trace("attempting to get session; create = " + create +
"; session is null = " + (this.session == null) +
"; session has id = " + (this.session != null && session.getId() != null));
}
if (this.session == null && create) {
if (!isSessionCreationEnabled()) {
String msg = "Session creation has been disabled for the current subject. This exception indicates " +
"that there is either a programming error (using a session when it should never be " +
"used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
"for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " +
"for more.";
throw new DisabledSessionException(msg);
}
log.trace("Starting session for host {}", getHost());
SessionContext sessionContext = createSessionContext();
Session session = this.securityManager.start(sessionContext);
this.session = decorate(session);
}
return this.session;
}
這裡傳入的引數create為false,因此不會構造session,因此返回null。本章最後假設create為true,分析如何構造該Session。返回到mergePrincipals函式,因為session和currentPrincipals都為null,因此直接返回。所以第一次進入mergePrincipals函式,什麼都沒做。
回到saveToSession函式,再看mergeAuthenticationState函式,
protected void mergeAuthenticationState(Subject subject) {
Session session = subject.getSession(false);
if (session == null) {
if (subject.isAuthenticated()) {
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
}
} else {
Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
if (subject.isAuthenticated()) {
if (existingAuthc == null || !existingAuthc) {
session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
}
} else {
if (existingAuthc != null) {
session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
}
}
}
}
和前面的分析類似,這裡也什麼也沒做。
bind
回到最前頭看SecurityUtils的getSubject函式,最後呼叫了ThreadContext的bind函式,
public static void bind(Subject subject) {
if (subject != null) {
put(SUBJECT_KEY, subject);
}
}
看一下put,定義在ThreadContext中,
public static void put(Object key, Object value) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
if (value == null) {
remove(key);
return;
}
ensureResourcesInitialized();
resources.get().put(key, value);
if (log.isTraceEnabled()) {
String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
}
ensureResourcesInitialized就是初始化resources了,
private static void ensureResourcesInitialized(){
if (resources.get() == null){
resources.set(new HashMap<Object, Object>());
}
}
因此最後就是簡單地將剛剛構造的Subject(其實是DelegatingSubject)放入resources中。