Shiro源码分析之Subject和SecurityManager
Subject
毫无疑问,Subject是Shiro最重要的一个概念。
“Subject”只是一个安全术语,意味着应用程序用户的特定于安全性的“视图”。Shiro Subject实例代表单个应用程序用户的安全状态和相关操作。
创建
初次创建是在AbstractShiroFilter#doFilterInternal方法中:
final Subject subject = createSubject(request, response);
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
创建的时候传入安全管理器,Subject.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);
}
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
这个安全管理器还是我们指定的那个DefaultWebSecurityManager,一路传过去的。这个subjectContext参数是一个DefaultSubjectContext,子接口中的Builder覆盖了父类的方法,实际赋予的是一个DefaultWebSubjectContext。
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
subjectFactory是安全管理器DefaultWebSecurityManager中默认的DefaultWebSubjectFactory:
public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}
最终的创建又回到:
public class DefaultWebSubjectFactory extends DefaultSubjectFactory {
public DefaultWebSubjectFactory() {
super();
}
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);
}
//......
}
根据相关属性new出来一个WebDelegatingSubject。
shiro中很多都是这样的继承和组合关系:
DefaultSecurityManager -> DefaultSubjectFactory -> DelegatingSubject
DefaultWebSecurityManager -> DefaultWebSubjectFactory -> WebDelegatingSubject
再回到创建的方法:
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;
}
});
这个execute方法是在DelegatingSubject中实现的:
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首先构造了一个ThreadState:
public SubjectCallable(Subject subject, Callable<V> delegate) {
this(new SubjectThreadState(subject), delegate);
}
associated.call()调用ThreadState.bind():
public V call() throws Exception {
try {
threadState.bind();
return doCall(this.callable);
} finally {
threadState.restore();
}
}
SubjectThreadState的bind方法:
public void bind() {
SecurityManager securityManager = this.securityManager;
if ( securityManager == null ) {
//try just in case the constructor didn't find one at the time:
securityManager = ThreadContext.getSecurityManager();
}
this.originalResources = ThreadContext.getResources();
ThreadContext.remove();
ThreadContext.bind(this.subject);
if (securityManager != null) {
ThreadContext.bind(securityManager);
}
}
这样就妥妥地把当前subject和线程绑定到了一起(还有securityManager)。
在这里遇到一个问题,记录下:
https://www.oschina.net/question/2275855_2273492
其实这只是每次进入核心过滤器时默认为我们创建的一个Subject,当调用subject.login方法之后会再次创建一个Subject,后面登录部分会做详细介绍。
获取
Subject subject = SecurityUtils.getSubject();
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
绑定是通过ThreadContext,获取当然也是从其取。
public abstract class ThreadContext {
private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
private static Object getValue(Object key) {
return resources.get().get(key);
}
public static Object get(Object key) {
Object value = getValue(key);
return value;
}
public static Subject getSubject() {
return (Subject) get(SUBJECT_KEY);
}
}
最终是到ThreadLocal中拿,不过这个ThreadLocal是 InheritableThreadLocalMap 类型的(继承自InheritableThreadLocal)。
每个线程都有一个Map
SecurityManager
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="casRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
<!-- <property name="rememberMeManager" ref="rememberMeManager" /> -->
</bean>
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.ServletContainerSessionManager"/>
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
void logout(Subject subject);
Subject createSubject(SubjectContext context);
}
安全管理器继承了Authenticator, Authorizer, SessionManager三个接口。自顶向下第一个抽象类是CachingSecurityManager,接着是RealmSecurityManager,后面是AuthenticatingSecurityManager,AuthorizingSecurityManager,SessionsSecurityManager。
然后才是DefaultSecurityManager,DefaultWebSecurityManager。
由于是web项目,我们指定了DefaultWebSecurityManager,在构造器中会为我们设置相匹配的属性(都是和web相关的):
public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}
SessionsSecurityManager持有一个sessionManager对象,对sessionManager接口的实现是转移到这个对象上来的:
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
private SessionManager sessionManager;
public SessionsSecurityManager() {
super();
this.sessionManager = new DefaultSessionManager();
applyCacheManagerToSessionManager();
}
}
AuthenticatingSecurityManager,AuthorizingSecurityManager类似,这两个抽象管理器在无参构造函数中创建了默认的对象:
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
private Authorizer authorizer;
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}
}
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
private Authenticator authenticator;
public AuthenticatingSecurityManager() {
super();
this.authenticator = new ModularRealmAuthenticator();
}
}
所以这个安全管理器几乎承担了所有的操作,然后转移到具体的对象。它的层次结构非常清晰,职责分明。对Subject所有操作最终都会转移到SecurityManager。
推荐阅读
-
Swoft源码之Swoole和Swoft的分析
-
MapReduce之Job提交流程源码和切片源码分析
-
Android源码解析之应用程序框架层和系统运行库层日志系统分析
-
jQuery源码分析之构造jQuery对象-源码结构和核心函数
-
OkHttp源码分析之ConnectInterceptor和CallServerInterceptor
-
XML解析 验证之XSD和DTD验证以及 SpringXML验证源码分析
-
MapReduce之Job提交流程源码和切片源码分析
-
java定时任务_java中的任务调度之Timer定时器(案例和源码分析)
-
Swoft源码之Swoole和Swoft的分析
-
Shiro 之Subject、SecurityManager、Realm源码分析