org.apache.shiro.SecurityUtils#getSubject介绍

在shiro-web中,获取已验证的subject通常使用org.apache.shiro.SecurityUtils#getSubject方法:

1
2
3
4
5
6
7
8
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}

根据代码可以看出,该subject是和线程绑定的。但是web容器都是使用线程池处理request的,是否意味着每次处理完request都需要手动取消绑定线程呢?答案当然是否!

从源码中解惑

  • 首先查看org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
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;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
} catch (Throwable throwable) {
t = throwable;
}
if (t != null) {
if (t instanceof ServletException) {
throw (ServletException) t;
}
if (t instanceof IOException) {
throw (IOException) t;
}
//otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
String msg = "Filtered request failed.";
throw new ServletException(msg, t);
}
}

可以看出被AbstractShiroFilter拦截后,后续处理都会启动新线程。

  • 接着查看org.apache.shiro.subject.support.SubjectThreadState#bind
1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

可以看出进行线程绑定

  • 再看org.apache.shiro.subject.support.SubjectCallable#call
1
2
3
4
5
6
7
8
public V call() throws Exception {
try {
threadState.bind();
return doCall(this.callable);
} finally {
threadState.restore();
}
}

可以看出线程执行完后,进行了解绑,解绑代码如下:

1
2
3
4
5
6
public void restore() {
ThreadContext.remove();
if (!CollectionUtils.isEmpty(this.originalResources)) {
ThreadContext.setResources(this.originalResources);
}
}

至此整个流程已清晰明了。

总结

在shiro-web项目中不用管理subject的线程绑定,但在非web中是需要手动管理subject的线程绑定的。