前言
本文属于源码分析,只想了解Shiro使用的读者请略过。
Subject接口是Shiro中非常重要的接口,客户端通常会使用Subject的方法进行登录、登出、鉴权、角色判断等功能。在《Shiro学习(一)——Shiro配置与快速开始》中,我们就使用了其login接口。Subject的功能似乎非常强大,那么它到底是什么,为什么能做这么多事呢,值得我们分析一下它的来龙去脉。
Subject创建
在《Shiro学习(一)——Shiro配置与快速开始》为例,Subject通过SecurityUtils.getSubject()方法获取,如下:
Subject subject = SecurityUtils.getSubject();
深入到SecurityUtils的源码中看看:
public abstract class SecurityUtils {
//......
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
//......
}
ThreadContext是shiro封装的一个操作线程局部变量的抽象类。第6行通过getSubject()方法获取Subject。在测试的例子中,因为还没创建Subject,所以第7行判断为null,因此第8行创建subject。看看Subject.Builder()干了什么:
public interface Subject {
//......
public static class Builder {
private final SubjectContext subjectContext;
private final SecurityManager securityManager;
//......
public Builder() {
this(SecurityUtils.getSecurityManager());
}
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);
}
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
//......
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
}
}
初始化Builder主要做两件事,一是设置SecurityManager,二是创建DefaultSubjectContext作为SubjectContext。接着看看(new Subject.Builder()).buildSubject()怎么创建Subject。它调用了33-35行,34行返回一个由securityManager创建的subejct。因此我们要跟踪securityManager的createSubject方法,前面都是创建subject的外围,接下来才是创建subject对象的核心:
public class DefaultSecurityManager extends SessionsSecurityManager {
//......
protected SubjectContext copy(SubjectContext subjectContext) {
return new DefaultSubjectContext(subjectContext);
}
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;
}
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
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;
}
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;
}
protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
SessionKey key = getSessionKey(context);
if (key != null) {
return getSession(key);
}
return null;
}
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;
}
protected SubjectContext resolvePrincipals(SubjectContext context) {
PrincipalCollection principals = context.resolvePrincipals();
if (isEmpty(principals)) {
log.trace("No identity (PrincipalCollection) found in the context. Looking for a remembered identity.");
principals = getRememberedIdentity(context);
if (!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;
}
//......
}
创建subejct第一件工作是创建SubjectContext,就是subject上下文。第10行复制一份刚才Builder创建的SubjectContext。当然这种复制对subjectContext内部的Map中的对象是浅拷贝,这样使每一个复制出来的SubjectContext都包含了设置好的SecurityManager。12行确保SecurityManager已被设置到SubjectContext中。13行设置Session到SubjectContext中,37行判断SubjectContext是否已有Session,因为SubjectContext从Builder复制出来只包含了SecurityManager,所以是null,因此走43行。由于之前SubjectContext没有设置过Session,因此43行也是返回null。14行是处理Principal,因为之前创建的SubjectContext没有涉及Principal,因此14行是指也没有做任何操作。16行创建Subject,跳转到23行调用DefaultSubjectFactory的createSubject方法。跟踪DefaultSubjectFactory的createSubject方法:
public class DefaultSubjectFactory implements SubjectFactory {
//......
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);
}
//......
}
这里会把session、principals、authenticated、host、sessionCreationEnabled查出,并设置到DelegatingSubject对象中。因此Subject subject = SecurityUtils.getSubject();这个语句最终返回一个DelegatingSubject对象。对于本文的例子而言,session、principals、host均为null,sessionCreationEnabled为true,authenticated为false。