前一篇已经介绍过了shiroFilter(shiro的入口点,所有请求都会经该过滤器,然后找到对应的过滤器处理请求。)
// AbstractShiroFilter.doFilterInternal
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
// 注意这里会创建Subject对象,从而引出今天的主题Subject
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;
}
});
}
...//省略代码,不影响阅读
}
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
// 使用FilterChainResolver来获取代理过滤器链
FilterChain chain = getExecutionChain(request, response, origChain);
// 执行代理过滤器链,由上一篇可知,会先执行shiro的过滤器链,然后执行原过滤器链
chain.doFilter(request, response);
}
请注意上面的代码中有这么一行,是用来创建Subject对象的。
final Subject subject = createSubject(request, response);
那么Subject在shiro
框架中到底代表了什么?
Subject和Session的关系又是什么呢?
带着这两个疑问,我们逐一揭开其神秘面纱!
首先来看Subject的默认实现类DelegatingSubject
//Subject的默认实现类
public class DelegatingSubject implements Subject {
private static final Logger log = LoggerFactory.getLogger(DelegatingSubject.class);
private static final String RUN_AS_PRINCIPALS_SESSION_KEY =
DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY";
protected PrincipalCollection principals;//当前用户
protected boolean authenticated;//是否登陆认证过
protected String host;//主机地址
protected Session session;//Session引用
/**
* @since 1.2
*/
protected boolean sessionCreationEnabled;
protected transient SecurityManager securityManager;
由上可知Subject内部维护了当前登陆的用户,是否登陆认证成功,当前主机地址和Session的引用。
接下来我们看一下Subject创建的过程:
//AbstractShiroFilter.createSubject
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
``WebSubject继承了Subject,而
WebSubject.
Builder是
WebSubject的内部类,同样继承了Subject.
Builder`
//WebSuject.Builder的构造函数
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);
}
//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.");
}
//让securityManager放入SubjectContext上下文环境,使之贯穿Subject整个创建过程。
this.subjectContext.setSecurityManager(securityManager);
}
subjectContext
是构造Subject过程中,为Subject提供所需的上下文环境。
//WebSubject.Builder.buildWebSubject
public WebSubject buildWebSubject() {
Subject subject = super.buildSubject();
if (!(subject instanceof WebSubject)) {
String msg = "Subject implementation returned from the SecurityManager was..."
throw new IllegalStateException(msg);
}
return (WebSubject) subject;
}
WebSubject
.Builder
.buildWebSubject
调用父类的buildSubject()
方法。
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
DefaultSecurityManager
.createSubject
public Subject createSubject(SubjectContext subjectContext) {
//复制subjectContext实例
SubjectContext context = copy(subjectContext);
//确保subjectContext中存在SecurityManager实例,不存在则创建一个
context = ensureSecurityManager(context);
//在构造Subject之前,解析Session(通过SessionId)把它放入SubjectContext,Session的维护是交给
//专门的SessionManager来维护
context = resolveSession(context);
//同样的,在构造Subject之前,解析Principals把它放入SubjectContext,此过程有可能失败
//导致最终的SubjectContext缺少Principal信息。具体过程见下文
context = resolvePrincipals(context);
// 创建Subject,委托给专门的SubjectFactory来构造
// SubjectFactory接口的默认实现为DefaultWebSubjectFactory
// 观察其对createSubject方法的实现正式将会话域context这一路收集来的信息汇总生成一个WebDelegatingSubject实例(又增加一个中间层)。
Subject subject = doCreateSubject(context);
//保存subject在session中,以备将来会用到(开启了rememberMe功能)
save(subject);
return subject;
}
解析Principals过程
//DefaultSecurityManager.resolvePrincipals
protected SubjectContext resolvePrincipals(SubjectContext context) {
//从SubjectContext中解析principals,详见下文
PrincipalCollection principals = context.resolvePrincipals();
if (isEmpty(principals)) {
//从SubjectContext中解析principals失败,尝试RemeberMe来获取principals
principals = getRememberedIdentity(context);
if (!isEmpty(principals)) {
context.setPrincipals(principals);
} else {
log.trace("No remembered identity found. Returning original context.");
}
}
return context;
}
从SubjectContext
中解析principals
//SubjectContext.resolvePrincipals
public PrincipalCollection resolvePrincipals() {
//首先查看当前上下文SubjectContext中是否存在Principals
PrincipalCollection principals = getPrincipals();
if (isEmpty(principals)) {
//check to see if they were just authenticated:
//不存在,尝试从上下文SubjectContext中的AuthenticationInfo中获取Principals
AuthenticationInfo info = getAuthenticationInfo();
if (info != null) {
principals = info.getPrincipals();
}
}
if (isEmpty(principals)) {
//AuthenticationInfo也解析失败,尝试从SubjectContext中的Subject解析Principals
Subject subject = getSubject();
if (subject != null) {
principals = subject.getPrincipals();
}
}
if (isEmpty(principals)) {
//Subject也解析失败,则尝试从session中获取当前的Principals
Session session = resolveSession();
if (session != null) {
principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
}
}
return principals;
}
由于session是shiro
的sessionManger
管理的,当用户登陆过了,就会通过sessionId
将Principals保存在session中,以便下次重新访问时,可以根据sessionId
获取当前的用户。
用户信息是存在session中的,但我们获取当前用户都是通过Subject来获取,因为Subject通过sessionId
取出对应 的用户信息,并放到当前Subject中,而Subject是通过ThreadLocal
模式将自身绑定到当前线程。