基本说明
filter执行的是代理bean的id为shiroFilter相应的方法
接下来看看 shiroFilter 是什么
再来分析shiroFilter 这个实现了 javax.servlet.Filter 的生命周期
spring 配置信息
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
<!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
<property name="successUrl" value="/index.action" />
<!-- 通过unauthorizedUrl指定没有权限操作时跳转页面 -->
<property name="unauthorizedUrl" value="/refuse.action" />
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!-- 静态资源放行 -->
/login/account = anon
/user = perms["user:view"]
<!--商品查询需要商品查询权限 ,取消url拦截配置,使用注解授权方式 -->
<!-- /itemEdit.action = perms[item:edit] -->
<!-- 请求 logout.action地址,shiro去清除session -->
/logout = logout
<!-- /** = authc 所有url都必须认证通过才可以访问 -->
/** = authc
<!-- 所有url都可以匿名访问 -->
<!-- /** = anon -->
</value>
</property>
</bean>
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入realm -->
<property name="realm" ref="customRealm" />
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- 会话管理器 -->
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 -->
<property name="globalSessionTimeout" value="600000" />
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true" />
</bean>
<!-- realm -->
<bean id="customRealm" class="org.apache.shiro.realm.text.IniRealm">
<constructor-arg index="0" value="classpath:shiro.ini"/>
</bean>
获取shiroFilter
ShiroFilterFactoryBean 实现了 FactoryBean ,那么实际上getBean的时候,返回的是getObject的内容而不是ShiroFilterFactoryBean
(如果不理解实现 FactoryBean 会返回 getObject 的同学, 期待接下来的spring源码分析,或者自行百度)
接下来分析 ShiroFilterFactoryBean 的getObject 返回的对象是什么,才能知道 init,doFilter,destory 的时候做了什么
ShiroFilterFactoryBean
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance();
}
return instance;
}
级别2
instance = createInstance();
- 验证securityManager不为空,验证 securityManager 必须是 WebSecurityManager
- 创建 过滤链管理器(责任链模式),将shiro默认的验证类型和用户自己定义的过滤器添加到管理器
- 返回一个 SpringShiroFilter 对象
ShiroFilterFactoryBean
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);
}
所以在web中配置的 shiroFilter 实际上是一个 SpringShiroFilter 对象,我们要分析的shiro在web中的使用,也就是分析 SpringShiroFilter 的生命周期
shiroFilter
init
- 设置filterConfig和servletContext
- 确保 this.securityManager 已被初始化
SpringShiroFilter
public void setFilterConfig(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
setServletContext(filterConfig.getServletContext());
}
public final void init(FilterConfig filterConfig) throws ServletException {
setFilterConfig(filterConfig);
try {
onFilterConfigSet();
} catch (Exception e) {
if (e instanceof ServletException) {
throw (ServletException) e;
} else {
if (log.isErrorEnabled()) {
log.error("Unable to start Filter: [" + e.getMessage() + "].", e);
}
throw new ServletException(e);
}
}
}
级别2
onFilterConfigSet();
- 当 init-param 的 staticSecurityManagerEnabled 不为空就设置 this.staticSecurityManagerEnabled
- init是空的,留给子类去实现,当前情况下没有子类去实现,就是空的
- 当 securityManager 为空的时候,创建默认的securityManager
- 判断 this.staticSecurityManagerEnabled 为true,就设置 SecurityUtils.securityManager
AbstractShiroFilter
// 当 init-param 的 staticSecurityManagerEnabled 不为空就设置 this.staticSecurityManagerEnabled
private void applyStaticSecurityManagerEnabledConfig() {
String value = getInitParam(STATIC_INIT_PARAM_NAME);
if (value != null) {
Boolean b = Boolean.valueOf(value);
if (b != null) {
setStaticSecurityManagerEnabled(b);
}
}
}
//当 securityManager 为空的时候,创建默认的securityManager
private void ensureSecurityManager() {
WebSecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
log.info("No SecurityManager configured. Creating default.");
securityManager = createDefaultSecurityManager();
setSecurityManager(securityManager);
}
}
protected final void onFilterConfigSet() throws Exception {
//added in 1.2 for SHIRO-287:
applyStaticSecurityManagerEnabledConfig();
init();
ensureSecurityManager();
//added in 1.2 for SHIRO-287:
if (isStaticSecurityManagerEnabled()) {
SecurityUtils.setSecurityManager(getSecurityManager());
}
}
doFilter
父类 OncePerRequestFilter 实现了 doFilter
- 判断是否有 filterName().FILTERED,不为空就 执行下一个过滤器
- 判断 没有启用过滤器 或者 request 没有过滤器就执行下一个过滤器
- 设置 filterName().FILTERED = true, 执行当前过滤器
OncePerRequestFilter
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName());
filterChain.doFilter(request, response);
} else //noinspection deprecation
if (/* added in 1.2: */ !isEnabled(request, response) ||
/* retain backwards compatibility: */ shouldNotFilter(request) ) {
log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.",
getName());
filterChain.doFilter(request, response);
} else {
// Do invoke this filter...
log.trace("Filter '{}' not yet executed. Executing now.", getName());
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(request, response, filterChain);
} finally {
// Once the request has finished, we're done and we don't
// need to mark as 'already filtered' any more.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
级别2
doFilterInternal(request, response, filterChain);
- 用 ShiroHttpServletRequest 包装servletRequest,ShiroHttpServletResponse 包装servletResponse (这是一个典型的装饰模式)
- 创建当前用户 Subject
- 当前用户执行访问目标操作
在这里创建用户的部分可以参考shiro 基础章节, 就是从 this.securityManager.createSubject(this.subjectContext);
访问目标 subject.execute 就是接下来进行重点讲解的了
AbstractShiroFilter
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);
}
}
级别3-1
subject.execute
这里就是一个很典型的代理模式 ,用 SubjectCallable 代理了原来的 callable.
然后再执行真正的call
public SubjectCallable(Subject subject, Callable<V> delegate) {
// 设置TheadState
this(new SubjectThreadState(subject), delegate);
}
public <V> Callable<V> associateWith(Callable<V> callable) {
//返回代理类
return new SubjectCallable<V>(this, callable);
}
public <V> V execute(Callable<V> callable) throws ExecutionException {
Callable<V> associated = associateWith(callable);
try {
// 代理类 call
return associated.call();
} catch (Throwable t) {
throw new ExecutionException(t);
}
}
级别3-2
updateSessionLastAccessTime(request, response);
通过名字就可以判断出来, 更新session的最后访问时间。
但是访问的时候带sessionId会报异常,不过没什么影响,可以忽略,或者关闭这个日志
http://localhost:8080/login.action;JSESSIONID=2bf853ec-f439-4981-8c01-7bfa7456c878
org.apache.shiro.session.UnknownSessionException: There is no session with id [b81ed452-882c-4cd1-a8e8-b1bfd7e3cee8]
at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170)
级别3-3
executeChain(request, response, chain);
这段内容也比较简单, 就是用当前的 FilterChainResolver 去获取 FilterChain ,
然后返回去执行 chain.doFilter(request, response);
这一段的重点内容也就在于 resolver.getChain(request, response, origChain);
这里会具体进行角色权限的判断,判断用户是不是有url的访问权限
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}
executeChain步骤(一)
级别1
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
}
FilterChain resolved = resolver.getChain(request, response, origChain);
if (resolved != null) {
log.trace("Resolved a configured FilterChain for the current request.");
chain = resolved;
} else {
log.trace("No FilterChain configured for the current request. Using the default.");
}
return chain;
}
级别2
FilterChain resolved = resolver.getChain(request, response, origChain);
获得当前链的操作 做了一下几个步骤
- 获取 FilterChainManager (在ShiroFilterFactoryBean的getObject下面的createInstance创建的)
- 获取 requestURI
- 遍历配置的 filterChainDefinitions 属性的key。 判断key是否能匹配requestURI。 如果能匹配 就返回value对应的Filter
接下来解析如何通过value获取对应的filter
如果有更多的兴趣: 可以去查看 如何匹配请求uri的filter
PathMatchingFilterChainResolver
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
String requestURI = getPathWithinApplication(request);
//the 'chain names' in this implementation are actually path patterns defined by the user. We just use them
//as the chain name for the FilterChainManager's requirements
for (String pathPattern : filterChainManager.getChainNames()) {
// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. " +
"Utilizing corresponding filter chain...");
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
级别3
return filterChainManager.proxy(originalChain, pathPattern);
这里就是通过 配置的key获取对应的Filter
例如: /* = authc 就是通过/* 获取shiro内置权限认证对象: NamedFilterList
DefaultFilterChainManager
public NamedFilterList getChain(String chainName) {
return this.filterChains.get(chainName);
}
public FilterChain proxy(FilterChain original, String chainName) {
NamedFilterList configured = getChain(chainName);
if (configured == null) {
String msg = "There is no configured chain under the name/key [" + chainName + "].";
throw new IllegalArgumentException(msg);
}
return configured.proxy(original);
}
级别4
return configured.proxy(original);
这里可以看到,是通过 NamedFilterList 和 FilterChain 创建了一个代理对象 ProxiedFilterChain
SimpleNamedFilterList
public FilterChain proxy(FilterChain orig) {
return new ProxiedFilterChain(orig, this);
}
executeChain步骤(二)
chain.doFilter(request, response);
通过 executeChain步骤(一) 就已经获取到了 FilterChain 对象。
返回的是一个 ProxiedFilterChain 对象。
这一步实际上跟进的就是 ProxiedFilterChain.doFilter
到这里为止,其实就已经分析完成了。因为类似: /** = authc 可以有各种各样的配置。
在最下面的附注会附上 perms 和 authc 的doFilter 分析
ProxiedFilterChain
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.filters == null || this.filters.size() == this.index) {
//we've reached the end of the wrapped chain, so invoke the original one:
if (log.isTraceEnabled()) {
log.trace("Invoking original filter chain.");
}
this.orig.doFilter(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Invoking wrapped filter at index [" + this.index + "]");
}
this.filters.get(this.index++).doFilter(request, response, this);
}
}
destory
卸载的时候什么都不做
AbstractFilter
public void destroy() {
}
附注
当前用户调用代理
做的事情非常简单,就是在执行目标前后进行 threadState 的设置和还原
其实就是设置当前线程的 Subject 和 SecurityManager
SubjectCallable
protected V doCall(Callable<V> target) throws Exception {
return target.call();
}
public V call() throws Exception {
try {
threadState.bind();
return doCall(this.callable);
} finally {
threadState.restore();
}
}
路径匹配
默认过滤器
shiro内置权限认证对象
认证过滤器
authc对应的filter: FormAuthenticationFilter
FormAuthenticationFilter 继承 OncePerRequestFilter 那么 doFilter 实际上就是执行 doFilterInternal
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//TODO 省略
doFilterInternal(httpRequest, httpResponse, filterChain);
}
级别2
doFilterInternal(httpRequest, httpResponse, filterChain);
这里的代码整体流程也非常清晰
1. 预处理请求 (进行url匹配,匹配成功后进行认证。 认证成功后 continueChain=true)
2. 通过访问控制器判断是否要执行调用链,
3. 清理调用链 (在这里什么都没做)
那么这里就可以确定最最重要的部分 perHandler
AdviceFilter
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
boolean continueChain = preHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]");
}
if (continueChain) {
executeChain(request, response, chain);
}
postHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Successfully invoked postHandle method");
}
} catch (Exception e) {
exception = e;
} finally {
cleanup(request, response, exception);
}
}
级别3
boolean continueChain = preHandle(request, response);
- 这里判断是否存在当前匹配器 ,例如 全局都没有配置 /user = authc, 那么就表示没有authc匹配器
- 遍历匹配器的key,用请求的uri和配置的匹配器进行匹配
- 进入下一阶段获取是否可以继续执行filterChain
PathMatchingFilter
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately.");
}
return true;
}
for (String path : this.appliedPaths.keySet()) {
// If the path does match, then pass on to the subclass implementation for specific checks
//(first match 'wins'):
if (pathsMatch(path, request)) {
log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path);
Object config = this.appliedPaths.get(path);
return isFilterChainContinued(request, response, path, config);
}
}
//no path matched, allow the request to go through:
return true;
}
级别4
return isFilterChainContinued(request, response, path, config);
onPreHandle 由 访问控制器分析 实现.
PathMatchingFilter
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
String path, Object pathConfig) throws Exception {
if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
if (log.isTraceEnabled()) {
log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}]. " +
"Delegating to subclass implementation for 'onPreHandle' check.",
new Object[]{getName(), path, pathConfig});
}
//The filter is enabled for this specific request, so delegate to subclass implementations
//so they can decide if the request should continue through the chain or not:
return onPreHandle(request, response, pathConfig);
}
if (log.isTraceEnabled()) {
log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}]. " +
"The next element in the FilterChain will be called immediately.",
new Object[]{getName(), path, pathConfig});
}
//This filter is disabled for this specific request,
//return 'true' immediately to indicate that the filter will not process the request
//and let the request/response to continue through the filter chain:
return true;
}
访问控制器
- 判断是否允许访问, 允许访问就返回true
- 拒绝访问(无论如何都返回false)
接下来就要进行两部分分析了 isAccessAllowed 和 onAccessDenied
AccessControlFilter
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
isAccessAllowed
- 判断subject 是否已经认证
- 判断是不是loginUrl
- 判断是否有授权
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
//判断subject 是否已经认证
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
//判断是不是loginUrl
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
return pathsMatch(getLoginUrl(), request);
}
protected boolean isPermissive(Object mappedValue) {
if(mappedValue != null) {
String[] values = (String[]) mappedValue;
return Arrays.binarySearch(values, PERMISSIVE) >= 0;
}
return false;
}
// 是否有访问权限,
protected boolean isPermissive(Object mappedValue) {
if(mappedValue != null) {
String[] values = (String[]) mappedValue;
return Arrays.binarySearch(values, PERMISSIVE) >= 0;
}
return false;
}
onAccessDenied
调用子类实现,发现子类无论如何都会返回false
也就是说 当isAccessAllowed的结果为false(不允许访问的时候)
1. 当前用户的凭证信息为空的时候, 跳转到登录页
2. 判断是否已经配置 unauthorizedUrl (未授权跳转到的url),如果已经配置就跳转到 unauthorizedUrl, 如果没有配置没权限页面,返回错误码401 (HttpServletResponse.SC_UNAUTHORIZED)
AccessControlFilter
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return onAccessDenied(request, response);
}
AuthorizationFilter
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = getSubject(request, response);
// If the subject isn't identified, redirect to login URL
if (subject.getPrincipal() == null) {
saveRequestAndRedirectToLogin(request, response);
} else {
// If subject is known but not authorized, redirect to the unauthorized URL if there is one
// If no unauthorized URL is specified, just return an unauthorized HTTP status code
String unauthorizedUrl = getUnauthorizedUrl();
//SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
return false;
}