项目中我们习惯直接使用SecurityUtils.getSubject() 获取当前登录用户的信息
/**
* 获取当前登录者对象
*/
public static Principal getPrincipal(){
try{
Subject subject = SecurityUtils.getSubject();
Principal principal = (Principal)subject.getPrincipal();
if (principal != null){
return principal;
}
// subject.logout();
}catch (UnavailableSecurityManagerException e) {
}catch (InvalidSessionException e){
}
return null;
}
翻看代码,我们很容易看到这里使用了ThreadContext模式,直接从当前线程里拿subject,但是我们知道tomcat为每个请求都会分配一个线程,那这是如何获取当前登录用户信息的呢?
这个问题的答案要从shiro请求的拦截器开始寻找
我们都知道在web.xml中配置的拦截器通过name与spring中配置的实现bean相对应
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 安全认证过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /><!--
<property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> -->
<property name="loginUrl" value="${adminPath}/login" />
<property name="successUrl" value="${adminPath}?login" />
<property name="filters">
<map>
<entry key="cas" value-ref="casFilter"/>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="user" value-ref="userFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<ref bean="shiroFilterChainDefinitions"/>
</property>
</bean>
通过ShiroFilterFactoryBean的getObject,返回SpringShiroFilter对象注入到spring中
通过以下源码我们看到这里主要的处理就是获取SecurityManager、初始化过滤器链,这里我们不在详述
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
//Expose the constructed FilterChainManager by first wrapping it in a
// FilterChainResolver implementation. The AbstractShiroFilter implementations
// do not know about FilterChainManagers - only resolvers:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
//injection of the SecurityManager and FilterChainResolver:
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
我们分析SpringShiroFilter的父类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);
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);
}
}
其中updateSessionLastAccessTime维持session有效,executeChain去执行匹配的过滤器。
在这之前createSubject先创建了Subject,再通过execute绑定到线程
跟到代码,我们可以看到绑定执行的代码块
org.apache.shiro.subject.support.SubjectThreadState中
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);
}
}
我们可以看到,先解绑然后绑定。
至此真相大白:
每个shiro拦截到的请求,都会根据seesionid创建Subject,清除当前线程的绑定,然后重新绑定的线程中,之后执行过滤器。
所以我们再SecurityUtils.getSubject()中获取的一直是当前用户的信息。