一.Shiro登陆认证原理

对于登陆请求(URL=/login.html),我们一般使用"anno"拦截器去过滤处理,而这个filter又是放行所有资源的(不作任何处理),所以登陆请求会直接到达Controller的登陆方法,所以我们直接从 Subject.login(token) 开始分析。

1.交由securityManager执行login(token)

2.调用authenticate()方法进一步获取认证信息

3.调用ModularRealmAuthenticator的doAuthenticate方法选择Realm

4.调用我们自定义Realm的认证方法获取数据库真实信息

二.Shiro内置过滤器详解

Shiro在Web应用中对于不同请求的认证和权限管理是通过Filter过滤器拦截来实现的,Shiro中有很多不同功能的过滤器,比如我们常用的 "anno"、"authc"等。在这里我们接着 一 中对认证登陆原理的分析,我们继续来分析一下专门实现认证登陆管理的"authc"过滤器。

1、分析目标

     Shiro包含很多内置的过滤器,各过滤器如下所示。这里只分析平时用的最多的一个 authc 过滤器,其对应的处理器的类是FormAuthenticationFilter。authc过滤器的作用是拦截请求,只有认证登陆了的用户才能访问目标资源,否则跳回登陆页。

2、继承关系及结构

    authc的类继承关系如下: 1.AbstractFilter > 2.NameableFilter > 3.OncePerRequestFilter > 4.AdviceFilter > 5.PathMatchingFilter > 6.AccessControlFilter > 7.AuthenticationFilter > 8.AuthenticatingFilter > 9.FormAuthenticationFilter

3、FormAuthenticationFilter过滤器详细分析

    功能原理:该Filter只对Request进行认证处理,并不涉及授权处理。若已认证则直接通过到达目标页面,若没认证失败则执行onAccessDenied方法:
(1)若该请求为登陆请求(URL=/login.html),则自动执行 subject.login 方法去尝试登陆。(这一步一般不会走的,因为URL=login.html的请求会直接被我们配置的anno filter拦截通过,去controller中执行登陆)
(2)若不是登陆请求,则直接重定向到 login.html 页面进行登陆,登陆又会重复一的操作

(1)、AbstractFilter

该过滤器位于shiro过滤器的最顶层,本质上是个抽象类,直接继承了Filter。它只做了一些基本的初始化操作,并没有实现Filter中的doFilter()核心过滤处理方法。

总结:AbstractFilter是最顶层抽象类,只做了一些基本的初始化操作,并没有过多的逻辑在里面。

(2)、NameableFilter

该过滤器允许通过getName和setName方式给过滤器命名。如果没有给该过滤器命名,那么默认将会使用web.xml中给定的名称(FilterConfiger的filterName)。

总结:NameableFilter可以理解为是给过滤器命名用的,也没有实现Filter中的doFilter核心处理逻辑。

(3)、OncePerRequestFilter

该抽象类才是正真实现了Filter中的doFilter方法的类。它用来保证在每个servlet容器上,每个请求都只会被过滤一次,不会被重复过滤处理。其通过getAlreadyFilteredAttributeName()方法来鉴别请求是否已经被过滤过,默认的实现基于具体过滤的实例名称。接下来看看具体逻辑:

  <1>、 判断过滤器是否已经执行过,如果执行过,调用filterChain.doFilter(request, response)直接放行,不再重复执行该过滤器的处理逻辑,直接走下一个过滤器或者通过该过滤进入到实际请求方法中。

  <2>、 判断过滤器是否开启,该类有个成员变量eabled,默认为true(注释给的解释是大多数的过滤器都是希望开启的,所以默认值为true),如果该过滤器没有被开启,也同上面的逻辑一样,直接走下一个过滤器或者通过该过滤进入到实际请求方法中。

 <3>、执行具体的请求拦截和逻辑处理,这里又分3步:

1、  设置一个已经执行过滤器的属性名称在request中(打上处理标记,防止重复)。

2、  调用doFilterInternal方法,执行真正的过滤器处理逻辑。(重点)

3、  这个过滤器处理完后,将过滤器的属性名称从request中移出。这样如果程序执行到第2步,过滤又被调用了,它将会走到第一个if中,直接略过这个过滤器的处理,这样就保证了,每个过滤器在处理一个请求的时候只会被执行一次。

总结:OncePerRequestFilter 保证了每个请求只会被该过滤器过滤和处理一次,并提供了一个实际处理过滤业务的方法doFilterInternal,但是该方法并没有实现。

(4)、AdviceFilter

这个过滤器实现了doFilterInternal的处理逻辑,并且类似于开启了AOP环绕通知,提供了preHandlepostHandleafterCompletion 这三个方法,用于进行过滤的前置、后置处理。

executeChain方法中的实现为:chain.doFilter(request, response) 继续执行下一个过滤器链,所以如果:

- preHandle方法返回false,则说明过滤器不会执行chain.doFilter,意味着请求被拦截掉了,不会进入到用户请求的地址上去。

- preHandle方法返回true,表示过滤器放行了过滤的逻辑通过请求。然后会执行postHandle方法(该类中的postHandle方法为空实现),这个方法用于对一些异常进行处理(目前也是空实现)。

通过以上可以看出,过滤的逻辑代码实际上是在preHandle这个方法中处理实现的,这个方法的返回值true和false决定了请求放行和请求被拦截。

总结:AdviceFilter提供了类似于AOP环绕通知式的编程方式,其处理拦截的逻辑是在preHandle方法中完成的。preHandle方法返回true和false代表了通过过滤,请求可以到达用户的请求地址和过滤器拦截掉了用户的请求。

(5)、PathMatchingFilter

这个过滤器用来匹配和筛选指定路径的请求,和对其它路径的请求放行。打个比方:如果配置如下拦截:/hello=authc,这就意味着,请用户请求/hello时,这时的authc过滤器就会对这个请求拦截并进行过滤逻辑处理。如果一个用户请求是/world,则该过滤器不会对此请求进行过滤处理。

这里有2个处理逻辑: 首先判断appliedPaths若为空,则放行请求,不作拦截请求的处理。这里的appliedPaths是一个地址过滤器的映射map。其中key存放的是请求的URL,value是对应该URL处理的Filter过滤器,这个 value也可以是空值。

经过debug发现,appliedPaths的value都为null,只有keySet是有用的。所以appliedPaths在该filter内的作用就是用来存储该filter所需要拦截处理的所有url路径集合,在过滤请求操作中用来判断当前请求的url是否与该过滤器匹配,是否需要进行拦截处理。

然后如果用户的请求与配置中拦截的请求匹配,则会调用isFilterChainContinued方法进行下一步处理。

在该方法中,首先使用isEnabled方法判断当前拦截器是否开启,如果拦截器是开启的,则调用方法onPreHandle 进行拦截的逻辑处理。

由上可知,PathMatchingFilter的onPreHandle方法默认返回true,即不对请求进行拦截处理直接放行,所以如果需要有自定义处理拦截的逻辑,需要由子类覆盖这个方法来完成。

总结:PathMatchingFilter对配置了url请求拦截的地址进行过滤器过滤,对没有配置拦截的url请求直接放行,不进行拦截。如果请求需要过滤,则处理过滤的逻辑由子类实现onPreHandle完成。

(6)、AccessControlFilter

如果用户没有认证(即登录),那么这个过滤器就是控制访问资源和用户重定向到登录页面的过滤器的父类。当一个用户没有认证(即登录)时,可以通过saveRequestAndRedirectToLogin这个方法,重定向到登录页面。

以上是该类的文档说明,实际还有一些其它的逻辑在里面,接着PathMatchingFilter的思路走,我们得看看该类中onPreHandle中的逻辑实现。

在onPreHandle中依次调用了两个方法的逻辑或,isAccessAllowed()和onAccessDenied()。这两个方法的执行顺序如下:

- 首先执行isAccessAllowed()方法,若该方法返回true,则onPreHandle直接通过,不再执行onAccessDenied,表示允许请求通过。

- 若isAccessAllowed()方法返回false,则继续执行onAccessDenied方法,该方法的结果将会决定请求是否通过。

isAccessAllowed方法:如果请求允许正常处理,返回true。否则返回false由方法onAccessDenied进行拒绝后的请求处理。该方法在AccessControlFilter中没有实现,需要子类覆盖。

onAccessDenied方法:即登录验证在isAccessAllowed方法中被拒绝以后调用,其中参数mappedValue可以通过配置获取到,也可以为null。大多数表示被isAccessAllowed方法拒绝后的行为(即isAccessAllowed返回false),再做处理时不需要mappedValue这个配置参数。该方法在AccessControlFilter中没有实现,需要子类覆盖。

总结:AccessControlFilter中的onPreHandle处理真正的拦截逻辑,提供了两个逻辑处理方法。其中isAccessAllowed方法一般验证用户是否登录,onAccessDenied处理用户没登录后的逻辑。在这个过滤器中并没有给出isAccessAllowed和onAccessDenied方法的实现,下一步得去子类中看。

(7)、AuthenticationFilter

该类是需要对当前用户执行认证的过滤器基类,这个类封装了一些检查用户是否在系统中已经验证过的逻辑,子类需要对未验证的请求执行特定的逻辑。

AuthenticationFilter这个类的子类为AuthenticatingFilter,通过第6个过滤器可以知道,最后处理登录认验的逻辑实际上就是isAccessAllowed和onAccessDenied这两个方法。再结合文档的描述可知,这个类实现了isAccessAllowed的逻辑,而它的子类AuthenticatingFilter实现了onAccessDenied的逻辑。先看isAccessAllowed方法:

这个方法很简单:先取出当前绑定的Subject对象,然后再调用isAuthenticated方法判断是否已经存在认证信息,若为true则说明当前用户已经认证过了。

通过以上逻辑可以看到,如果当前用户已认证(即用户已登录),那么用户的请户的将会直接往后执行,不再进行过滤拦截。如果用户没有登录,那么将会继续执行后面onAccessDenied方法中的内容。而onAccessDenied是在其子类中实现的。

总结:AuthenticationFilter实现了isAccessAllowed方法,如果用户已登录,那么过滤器将直接放行,如果用户没有登录,那么再由其子类中的onAccessDenied方法处理后续逻辑。

(8)、AuthenticatingFilter

该类会尝试基于用户的请求,自动的去执行一些身份认证操作。我们先看下这个过滤器所有的方法。

可以看到,该类并没有实现onAccessDenied方法。而是提供了一些登录,创建用户,和登录成功或失败后的重定向跳转等方法,并重写了isAccessAllowed方法。以下是登录方法executeLogin:

注意:在以上方法中,该类并没有提供createToken方法的具体实现,并且onLoginSuccess方法默认返回True,onLoginFailure方法默认返回False,来简单表示执行登陆操作是否成功。我们继续来看一下该类重写的isAccessAllowed方法:

可以看到,重写后的方法会先执行父类的isAccessAllowed方法,若用户已认证则直接通过。若返回false即当前subject未认证,才会继续执行第二部分。第二部分就是用来判断该请求是否是一个打了Permissive特权标记的非登陆请求,如果是则直接通过。

总结:AuthenticatingFilter提供了一些如登录,和重定向跳转的方法,并没有onAccessDenied方法的实现,那么这个实现就只能在最后一个子类FormAuthenticationFilter中完成了。

(9)、FormAuthenticationFilter

该类要求请求用户进行身份认证,以便使请求继续,如果没有认证,则强制让该请求重定向到你配置中的登录URL(loginUrl)。

这个构造器会构造一个UsernamePasswordToken对象,里面包含了username, password,和rememberMe这三个请求参数。当调用Subject.login(usernamePasswordToken)方法时,它会尝试自动的执行登录操作,要注意的是,这个尝试登录的操作仅仅只会在isLoginSubmission(request,response)返回true且是一个POST请求的登录操作。

如果尝试登录失败,则会将AuthenticationException异常写入到request的属性当中,这个属性的key是是failureKeyAttribute(这是个变量的名字,其值为shiroLoginFailure),FQCN能用作i18n的key或查找机制,向用户解释为什么会登录失败。(也就是说,如果被拦截,可以在request. getAttribute(“shiroLoginFailure”)中得到返回的错误消息)。

如果你想用你自己的代码处理身份认证和登录,可以用使用PassThruAuthenticationFilter,它允许loginUrl的请求直接传递到你自己的容器代码中去。这个文档说明比较长,我们这里主要看onAccessDenied的实现(当请求到达onAccessDenied时,说明当前subject是未认证登陆的)。

<1>、  isLoginRequest 判断当前请求是否是登录请求,如果不是登录请求,直接通过saveRequestAndRedirectToLogin方法重定向到登录页面(这里的登录请求指的是loginUrl,它有一个默认地址是/login.jsp)。其中saveRequestAndRedirectToLogin这个方法在AccessControlFilter中实现。

<2>、如果是登录请求(目标页面时loginUrl的请求),则通过isLoginSubmission判断是否是http的post请求。如果不是则说明该请求的是去往登录页面的get跳转请求,用户就是直接通过浏览器访问登录页面的,这时只需要返回true,放行就可以了。

<3>、如果isLoginSubmission返回true,表明用户是一个http的post表单登陆请求,并且是访问登录的url请求。那么就执行executeLogin(request, response)方法自动去尝试做登录操作。

executeLogin方法首先会从createToken方法中把请求request中的登陆信息参数(request头部的字段,我们一般不会执行它)取出封装为token,这个token中包含了用户名密码。

其中用户名,密码是一个固定的变量值字段username、password。

即页请求传过来的请求参数字段默认是username和password。createToken会尝试取出这2个参数自动封装成AuthenticationToken对象。然后根据这个对象执行登录操作。(一般这个操作都是会在我们自定义的realm中完成,这里一般不用),若登录成功,直接执行onLoginSuccess(token, subject, request, response)方法。

登录成功后执行issueSuccessRedirect方法重定向到登录成功的页面,然后返回false(这里返回false是因为用户是请求的登录操作,然后被authc过滤器给拦截掉并且已经登录成功了,就没必需继续再往后面走controller了),注意:这里的onLoginSuccess方法是FormAuthenticationFilter中的,不是AuthenticatingFilter中的。

登录失败则执行onLoginFailure方法,这个方向会在request中的属性中存入失败原因,key值为:shiroLoginFailure,并且最终返回true,意味着用户的登录请求最后到达到我们的后台,只是在过滤器中已经做过一次登录了,并且是登录失败,所以我们自己的登录地址在编码时不需要再重复做登录认证操作,只需要从request中取出shiroLoginFailure认证报错信息,做相应的逻辑处理就可以了。

 总结:FormAuthenticationFilter实现了认证失败后的处理逻辑,即用户在未登录情况下处理的后续操作,如果用户是一个非登录请求,那么会直接重定向到登录页面(即配置中的loginUrl页面),如果是一个登录请求,且是GET请页,那么直接放行登录请求,如果是POST请求,则会从请求中获取默认的username,password去尝试登录,如果登录成功,则由过滤器直接重定向到登录成功的url上去,并拦截掉用户的请求。如果登录失败,则由过滤器直接重定向到登录失败的url上去,并将失败信息写入到request中去,key值为shiroLoginFailure,然后过滤器放行,直接到达用户请求的地址,我们就可以在这个请求地址中取到登录失败的数据做相应的操作了。

 最后:梳理一下全部流程中方法的调用。首先过滤器会调用doFilter(在OncePerRequestFilter中)方法,然后再调用doFilterInternal方法(在AdviceFilter中),然后再调用preHandle、executeChain、postHandle(在AdviceFilter中)这3个方法,实际拦截业务在preHandle方法中,然后再调用onPreHandle(在AccessControlFilter中)方法,然后再调用isAccessAllowed(在AuthenticationFilter中)方法和onAccessDenied(在FormAuthenticationFilter中)方法。

三.Shiro授权原理分析

对于Shiro的授权管理,我们这里以RolesAuthorizationFilter为例分析,它所对应的filter字符串为 "roles[角色]"。该filter的功能为:拦截对应的url请求,判断该请求是否拥有目标角色的权限,若有则允许通过跳往目标界面。否则拒绝访问。

1.isAccessAllowed方法分析

RolesAuthorizationFilter继承自AuthorizationFilter,在该Filter中只实现了一个isAccessAllowed方法,我们来看看这个方法干了什么。

可以看到在该方法中,就是直接调用了subject.hasAllRoles方法判断权限,我们继续进去看一下是怎么判断权限的。

在该方法中,首先调用hasPrincipals方法判断用户是否已经认证登陆了(如果请求到达该filter之前已经登陆了,认证信息会被保存到subject中)。如果用户还未认证则直接返回false。否则,就继续执行调用securityManager的hasAllRoles方法来进行权限判断。我们继续深入。

进一步到AuthorizingRealm的hasAllRoles方法中,进行了上述的两步处理,所以重点是第一步的权限Info是如何获得的,我们继续跟进。

可以看到,最终执行的就是我们自定义Realm的doGetAuthorizationInfo方法,来获取用户所对应的真实权限信息,封装后返回给上层进行比对来判断是否符合权限管理。

2.onAccessDenied方法分析

如果isAccessAllowed方法返回false,即RolesAuthorizationFilter中的权限判断不通过,则会执行其父类AuthorizationFilter中的onAccessDenied方法,进行拒绝后的处理。

总结授权Roles原理:该Filter只对Request进行授权管理,不进行认证处理。

(1)执行isAccessAllowed方法:该方法将目标url设置的权限map 使用subject.hasAllRoles(roles);判断是否拥有权限 => 调用securityManager.hasAllRoles => 最终调用我们自定义Realm的doGetAuthorizationInfo返回用户所拥有的权限信息(若用户未登陆,认证信息为空则直接return false)

(2)执行onAccessDenied方法:若isAccessAllowed返回false则执行onAccessDenied,如果没登陆则直接跳转到login.html;否则就是没有权限,跳转到未授权提示页面

四.Shiro中Filter之间的执行顺序

在Shiro中,我们配置了很多Filter来帮我们完成各种场景下的认证和授权功能,我们的很多配置可能如下图所示,那么他们之间的执行有什么顺序或者关系吗,接下来我们来分析一下Filter的执行过程。

1.ShiroFilterFactoryBean分析

ShiroFilterFactoryBean是一个比较核心的类,它的主要作用是配置和管理Shiro的Filter,作为ShiroFilter的工厂类。除此之外,它内部有一个SpringShiroFilter内部类,该类有一个重要作用就是隔离Shiro配置的filter和外部servlet的filter:

  • 对外:表现为一个ShiroFilter整体,与其他SpringServlet的Filter共处一条FilterChain,依次进行请求的拦截和处理
  • 对内:管理和调度不同内部filter的执行,动态构造filterChain链,执行shiro内部的认证和权限过滤

2.SpringShiroFilter的请求处理

我们来看一下,当请求到达SpringShiroFilter后,SpringShiroFilter在内部是如何调度我们自己配置的filter去处理请求的。

拦截请求后,接着在SpringShiroFilter的doFilterInternal方法中来对请求进行具体的逻辑处理。可以看到该filter通过executeChain方法构造了一条内部filterChain链,来对请求进行进一步的内部处理。我们继续深入看看这条链是什么。

可以看到这条内部链是通过getExecutionChain方法构造的,我们进一步看看这条filterChain是如何构造的。

可以看到在构造filterChain时,依次遍历 ShiroFilterFactoryBean 配置的所有filter 有序集合,并比对目标url和配置filter的拦截路径,如果一旦匹配,则直接将第一个匹配的filter取出构造filterChain并返回,不再用其他的filter去匹配处理。

因此注意:

  • 配置filter时一定要使用LinkHashMap,因为其是有序的,所以shiroFilter会按照添加顺序建立内部FilterChain,来拦截过滤request。
  • 每个配置Filter并不是都依次执行的,而是只执行第一个匹配拦截成功的filter(会匹配url,选出第一个匹配的拦截filter构造一个Shiro的filterChain),若通过后则直接执行Servlet的Filter链 ,不再执行后续配置的filter。这就是我们说要把"anno"放在"authc"之前才能生效的原因。

Shiro(三) Shiro核心原理分析相关推荐

  1. RPC 实战与核心原理分析

    RPC 实战与核心原理分析 RPCX是一个分布式的Go语言的 RPC 框架,支持Zookepper.etcd.consul多种服务发现方式,多种服务路由方式, 例子 服务端 package maini ...

  2. 【计算机视觉】数字图像处理(三)—— 图像变换原理分析

    数字图像处理(三)-- 图像变换原理分析 一.图像变换的目的与用途 1. 图像变换的目的 2. 图像正交变换的要求 正交变换的应用 (一).傅里叶变换 1.傅里叶变换的理论基础与基本定义 2.连续函数 ...

  3. Spring IOC核心原理分析

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,本文系统分 ...

  4. 2021SC@SDUSC-PALISADE(三)BGV原理分析与python实现

    2021SC@SDUSC 目录 BGV原理分析 1 符号说明 2 多项式运算 3 Learning With Error 与 Ring Learning With Error 4 构建一个全同态体系 ...

  5. 玩转Koa之核心原理分析

    Koa作为下一代Web开发框架,不仅让我们体验到了async/await语法带来同步方式书写异步代码的酸爽,而且本身简洁的特点,更加利于开发者结合业务本身进行扩展. 本文从以下几个方面解读Koa源码: ...

  6. Spring AOP核心原理分析

    "横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面.所谓"切面" ...

  7. 以抖音为例,解构数据分析三个核心原理

    无论你是产品人还是运营人,只要与业务相关.与产品相关,你就少不了和数据分析打交道.一套优良的数据分析方法能够高效解决很多问题,并在产品决策/产品运营遇到瓶颈时提供优秀的解决方案. 数据分析方法已经不是 ...

  8. Apache Iceberg核心原理分析文件存储及数据写入流程

    点击上方蓝色字体,选择"设为星标" 回复"面试"获取更多惊喜 全网最全大数据面试提升手册! 第一部分:Iceberg文件存储格式 Apache Iceberg作 ...

  9. OrchardCore实现模块化核心原理分析

    [导读]ABP vNext并未过多探究,当然其基于DDD理念分层清晰,灵活性.扩展性自然也不在话下,但有些情况下我可能会首选OrchardCore,并非ABP vNext不可 若改造项目,也因历史遗留 ...

最新文章

  1. 王可汗:数据科学带我解开摩擦学的“反常现象” | 提升之路系列(七)
  2. 使用正则替换文件头注释
  3. SpringQuartz定时任务调度器
  4. GitHub 为什么有些时候进去特变慢
  5. 神奇的机器人评课_《聪明的机器人》教学反思
  6. 前端服务器获取js文件偶尔慢_我所认识的前端性能优化
  7. P2336-[SCOI2012]喵星球上的点名【SA,树状数组】
  8. C++ 常用设计模式
  9. 原生js实现浏览器全屏和退出全屏
  10. 开源 java CMS - FreeCMS1.9发布。
  11. c++11 多线程编程(六)------条件变量(Condition Variable)
  12. 织梦后台界面修改方法
  13. web版的在线绘图工具
  14. python request 报错 #No JSON object could be decoded
  15. java scanner输入数组_Java Scanner输入两个数组的方法
  16. C#实现的系统内存清理
  17. 【R实验.9】主成分和因子分析
  18. 计算机窗口键,电脑上win是哪个键_电脑windows键是哪个-win7之家
  19. 1.7 使用不同设备类型的iOS模拟器 [原创iOS开发-Xcode教程]
  20. moment.js时间操作后24小时制变成了12小时制

热门文章

  1. 蚁群算法简介及matlab源代码
  2. 怎么自制小程序?【自己制作小程序】
  3. 苹果笔的代替笔有哪些?Ipad好用电容笔测评
  4. 生成drl文件_drools原生drl规则文件的使用
  5. LeetCode 28 Implement strStr()(实现strStr()函数)
  6. php+mysql企业员工培训管理系统dzkf87
  7. BlockingQueue实例
  8. C++的STL中accumulate函数用法
  9. Aspose.Words控件支持DOC,OOXML,RTF,HTML,OpenDocument,PDF,XPS,EPUB和其他格式
  10. 大数据分析及挖掘技术