shiro本质上是基于一系列filter的,根据不同的路径进入不同的filter,进行不同的逻辑处理,并决定是否要继续执行下一个filter。
因此我们就可以控制整个应用程序的流转。(默认的在 DefaultFilter 中共12个)
下面简单理一下shiro的执行逻辑:
-
因为shiro是基于filter的,我们先理一下它的filter的类结构。
在ShiroConfig中都会配置 ShiroFilterFactoryBean ,它是一个工厂bean,返回 的是SpringShiroFilter 。filter中最重要的自然是 doFilter() 方法,在 SpringShiroFilter 中查找这个方法,最终定位到 OncePerRequestFilter 。这个类我们可以把它当作整个shiro处理流程的基类。
-
我们先不加入自定义filter,用它自带的authc filter 来跟一下源码
1
2
3
Map<String, String> map = new LinkedHashMap<>();
map.put("/**", "authc");
factory.setFilterChainDefinitionMap(map);
再定义一个简单的api
1
2
3
4
5
6
7
8
9
@GetMapping("/article")
public InvokeResult<String> article() {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
return InvokeResult.success("You are already logged in", null);
} else {
return InvokeResult.success("You are guest", null);
}
}
接下来访问/article,自然进入到了之前说过的 OncePerRequestFilter 中的 doFilter() 方法
可以看到当前的对象是 ShiroFilterFactoryBean 中的 SpringShiroFilter。
接着往下走,到了 doFilterInternal() 方法
step into ,继续,进入到了 AbstractShiroFilter 中的 doFilterInternal() 。OncePerRequestFilter 有2个子类,一个是 AbstractShiroFilter,一个是 AdviceFilter。记住这个继承结构,很重要。
简单来说 AbstractShiroFilter 用于执行初始化、配置、构造subject等事情,AdviceFilter 通过 preHandle()、postHandle() 方法执行真正的filter逻辑,并决定要不要继续执行下一个filter。
继续往下走:
可以看到先创建了一个subject,这个subject将会贯穿这个request的整个生命周期,每次请求进来shiro都会创建一个subject,具体创建过程后面再讲。
接下来执行过滤链,也就是 executeChain() 方法。
step into:
第一步先获取过滤链,我们先看一下获取到的是什么东西
获取到的是一个 ProxiedFilterChain,从名字可以看出来它是一个代理过滤链。它代理的是什么呢?从它的成员属性来看,代理的是原来的过滤链(orig),再加上了一个filterList,这个filterList中看到了我们熟悉的 authc 字眼。所以这一块的作用就是将请求从容器中的 filter 代理到了 shiro 中的filter,接下来就可以执行我们的认证、授权逻辑了。
- 接着上一步,执行到了authc过滤器,也就是 FormAuthenticationFilter ,FormAuthenticationFilter 继承于 OncePerRequestFilter 下的 AdviceFilter,回想一下上面讲的继承结构,是不是感觉很清晰了。
同样,执行 FormAuthenticationFilter 中的 doFilter() 方法,由于它本身没有定义,所以我们又回到了 OncePerRequestFilter 中的 doFilter() ,然后又到了 doFilterInternal()
step into,由于这次是 FormAuthenticationFilter,所以这次的 doFilterInternal 进入到了 AdviceFilter中,记住这个类的继承结构,很重要。
接着往下走,可以看到有preHandle、postHandle、continueChain方法,所以可以大概猜测到它的作用是执行一些业务逻辑来决定要不要继续执行filter过滤链。举个例子,反应到前台就是当没有认证时就不继续执行过滤链,跳转到登录页。
接着往下走,我们step into preHandle() 方法
进入到了 PathMatchingFilter 类中的 preHandle,PathMatchingFilter 是AdviceFilter 的直接子类。可以看一下 FormAuthenticationFilter 的 类图:
PathMatchingFilter 顾名思义,就是根据当前请求的路径来判断是否要执行这个filter。如果没有匹配到,返回true,继续执行下一个filter。这里由于我们之前定义了 /**=authc,即所有路径都要过authc,所以这里匹配上了。
进入到了 isFilterChainContinued() 方法
到了 onPreHandle() 方法,这个方法在 AccessControlFilter 中实现,多回看上面的 FormAuthenticationFilter 类图。
可以看到它的执行逻辑是先执行 isAccessAllowed(),为true,直接返回,说明该请求可以被放行,然后继续执行下一个filter。如果为false,则执行后面的onAccessDenied() 方法。
接着step into isAccessAllowed(),到了 AuthenticatingFilter 中的 isAccessAllowed(),随时回看类结构图。
它的执行逻辑是先执行父类的isAccessAllowed(),如果为false,再判断是否是登录请求,isPermissive() 是否满足。
我们来看父类的isAccessAllowed(),也就是 AuthenticationFilter ,
可以看到它的逻辑就是判断subject有没有被认证,subject的principal是否存在。subject来自于之前 AbstractShiroFilter 中创建的,不记得的回看上面。很明显,这里没有认证,返回false。所以我们进入到了 onAccessDenied()。
onAccessDenied() 在 FormAuthenticationFilter 中被重写了:
它的逻辑是先判断是不是登录请求,如果不是则保存当前请求(用于登陆成功后的跳转)然后跳转到登陆地址;如果是登录请求并且是post方法,则执行executeLogin() 方法,如果不是post方法,直接返回true,这时用户看到的是登陆页面(登陆地址+get请求嘛)。整个流程差不多就是这样子。
-
接下来我们加入jwt
首先创建一个JWTFilter,继承自 BasicHttpAuthenticationFilter 。看一下类结构图。
根据上面的流程,我们只要重写 isAccessAllowed() 方法控制是否允许通过,重写 onAccessDenied() 方法 控制不允许通过后的逻辑即可。先上代码:
这里 isAccessAllowed() 直接返回false,以执行 onAccessDenied() 方法。在 onAccessDenied() 中,先判断是否是登录尝试,即请求头中是否带 Authorization 头
如果请求头中是否带 Authorization 头,执行 executeLogin() 方法,会在自定义的Realm中对token进行校验。
校验通过,返回true,接口得以正常访问。
校验不通过,捕获抛出的异常,通过 sendResponse(response, e.getMessage()) 发送到客户端。
如果不是登录请求,即请求头中没有带 Authorization 头,直接返回 false,并 sendResponse(response, “请携带token访问!”)。
因为在前后端分离中,每次请求都必须带上token,每次请求都必须对token进行校验,就相当于每次请求都要进行一次登录操作,这样理解就可以了。