`

Spring Security介绍(4)

阅读更多

7.4  保护Web应用程序

Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在你的应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。根据自己的需要,你 可以使用表7.4中所列的几个过滤器来保护自己的应用程序。

表7.4 Spring Security通过几个Servlet过滤器来控制对Web应用程序的访问

过滤器( org.acegisecurity.*

   

adapters.HttpRequestIntegrationFilter

使用来自 Web 容器提供的用户主体的信息填充安全上下文。

captcha.CaptchaValidationProcessingFilter

利用 Captcha 技术,帮助把一个用户识别为人(与自动化过程相对比)。 Captcha 是一种用来区分人类用户和自动化 / 计算机驱动用户的技术,它要求用户识别某个人类容易识别、但是计算机难于辨认出的东西(通常是一幅图像)。

concurrent.ConcurrentSessionFilter

确保一个用户没有在设置的时间内同时登录。

context.HttpSessionContextIntegrationFilter

使用从 HttpSession 获得的信息填充安全上下文。

intercept.web.FilterSecurityInterceptor

充当安全拦截器的角色,决定是否允许访问某一受保护的资源。

providers.anonymous.AnonymousProcessingFilter

用来将一个未经身份验证的用户识别为匿名用户。

续表

过滤器( org.acegisecurity.*

   

securechannel.ChannelProcessingFilter

确保正通过 HTTP HTTPS 发送一个请求(按照需求的要求)。

ui.basicauth.BasicProcessingFilter

尝试通过处理一个 HTTP 基本身份验证来验证一个用户的身份。

ui.cas.CasProcessingFilter

通过处理 CAS (中央认证服务)权证来验证一个用户的身份。

ui.digestauth.DigestProcessingFilter

尝试通过处理一个 HTTP 摘要身份验证来验证一个用户的身份。

ui.ExceptionTranslationFilter

处理当前过滤器链中其他任何过滤器所抛出的任一 AccessDeniedException AuthenticationException

ui.logout.LogoutFilter

用于将一个用户退出当前应用程序。

ui.rememberme.RememberMeProcessingFilter

自动验证已要求当前应用程序“记住”的用户身份。

ui.switchuser.SwitchUserProcessingFilter

用于断开某个用户。提供的功能与 Unix su 相似。

ui.webapp.AuthenticationProcessingFilter

接受当前用户的主体和凭证,并尝试验证该用户的身份。

ui.webapp.SiteminderAuthenticationProcessingFilter

通过处理 CA/Netegrity SiteMinder 标题来验证一个用户的身份。

ui.x509.X509ProcessingFilter

通过处理一个客户端 Web 浏览器提交的 X.509 证书来验证一个用户的身份。

wrapper.SecurityContextHolderAwareRequestFilter

servlet 请求增加一个请求外壳。

尽管表7.4列出了Spring Security提供的17种过滤器,但是对于绝大多数应用程序来说,只使用它们中的少数几个便已足够。特别地,在对一个Spring保护的Web应用程序提出请求时,它将经过至少下列四个过滤器(如图7.7所示):

 
图7.7  一个请求通过Spring Security
核心过滤器的流程。

1.由于HTTP本身没有国界,因此Spring Security需要以某种方式在Web请求之间保留一个用户的身份验证。集成过滤器负责在一个请求的开始时检索先前存储的认证信息(通常存储在HTTP 会话中),为Spring Security的其他过滤器进行处理做好准备。

2.接下来,某个认证处理过滤器将决定该请求是否是一个认证请求。如果是,有关的用户信息(通常为用户名/密码)就会被从这个请求中提取出来,然后 转交给认证管理器来确定用户的身份。如果这不是一个认证请求,那么认证处理过滤器不会执行任何处理,而该请求则会继续沿着过滤器链向下移动。

3.接下来的过滤器是例外转换过滤器。例外转换过滤器存在的唯一用途就是将可能已经被抛出的AccessDeniedException和 AuthenticationException转换成适当的HTTP响应。如果检测到一个AuthenticationException,那么当前请 求就会被发送到一个身份验证入口点(举例来说,登录界面)。如果被抛出的是一个AccessDeniedException,那么默认的行为将是向当前浏 览器返回一个HTTP 403错误。

4.所需的最后一个过滤器是过滤器安全拦截器。这个过滤器充当Web应用程序的安全拦截器(见7.1.1)。它的工作是检查这个请求,然后确定当前 用户是否具有访问被保护资源所必需的权限。不过,它并不是单独工作,它主要依靠认证管理器和访问决策管理器来帮助它授予或制止对被保护资源的访问。

如果当前用户成功通过了过滤器安全拦截器,那么他/她便将被授权可以访问那个受保护的Web资源。否则,将抛出一个AccessDeniedException,然后例外转换过滤器就会对它进行适当处理。

我们将逐一仔细研究这些过滤器。但是在能够开始使用它们之前,你还需要理解Spring Security是如何对Servlet过滤器使用一个Spring风格的技巧的。

 

7.4.1  代理Spring Security的过滤器

如果你曾经使用过Servlet过滤器,那么你知道要让它们生效,就必须在Web应用程序的web.xml文件中使用<filter> 和<filter-mapping>元素配置它们。虽然这样做能起作用,但是它并不适用于使用依赖注入进行的配置。

举例来说,假设你在自己的web.xml文件中声明了以下过滤器:

 

现在,假设那个FooFilter需要引用一个Bar Bean来完成它的工作。你该如何向FooFilter中注入Bar的一个实例呢?

简单的回答是:你无法做到这一点。web.xml文件没有依赖注入的概念,也没有直接的方式可以从Spring应用程序上下文中获取Bean,然后 将它们装配到一个servlet过滤器中。唯一可以选择的办法就是使用Spring的WebApplicationContextUtils从 Spring上下文中获取所需的bar Bean。举例来说,你可能会在那个过滤器的代码中加入以下内容:

 

不过,这种方法的问题是你必须在自己的Servlet过滤器中编写Spring特定的代码。此外,你在结束时还要硬编码一个指向那个bar Bean名称的引用。

幸运的是,Spring Security通过FilterToBeanProxy提供了一种更好的办法。

 

7.4.1.1  代理Servlet过滤器

FilterToBeanProxy是一个特殊的Servlet过滤器,它本身做的工作并不多,而是将自己的工作委托给Spring应用程序上下文 中的一个Bean来完成(如图7.8所示)。被委托的Bean几乎和其他的Servlet过滤器一样,实现javax.servlet.Filter接 口,但它是在Spring配置文件而不是web.xml文件中配置的。

 
(点击查看大图)图7.8  FilterToBeanProxy把过滤器处理代理给Spring应用
程序上下文中的一个委托过滤器Bean。

通过使用FilterToBeanProxy,就可以在Spring中配置实际的过滤器,充分利用Spring支持依赖注入的优势。这里的 web.xml文件只包含FilterToBeanProxy的<filter>声明。实际的FooFilter是在Spring配置文件中 配置的,并且它使用设置方法注入(setter injection)将一个对Bar Bean的引用设置到bar属性中。

要使用FilterToBeanProxy,必须在Web应用程序的web.xml文件中建立一个<filter>条目。举例来说,如果正在使用FilterToBeanProxy配置一个FooFilter,则可能应该使用以下代码:

 

在这里,targetClass初始化参数被设置为所委托过滤器Bean的完全匹配类名。当这个 FilterToBeanProxy被初始化时,它会在Spring上下文中查找一个类型为FooFilter的Bean,并且把自已的过滤工作委托给在 Spring上下文中找到的FooFilter Bean来完成:

 

如果找不到FooFilter Bean,则会抛出一个异常。如果找到了一个以上匹配的Bean,则会使用第一个找到的Bean。

可选地,你也可以通过设置targetBean初始化参数(而不是targetClass)来从Spring上下文中选取一个特定的Bean。举例来说,按照以下方式设置targetBean属性后,就可以根据名字挑选出fooFilter Bean来:

  

这里的targetBean初始化参数使得可以更具体地指定将过滤工作委托给哪个Bean来完成,但是需要精确匹配web.xml和Spring配 置文件中受委托Bean的名字。如果决定重新命名那个Bean时,就会带来额外的工作量。出于这个原因,使用targetClass(而不是 targetBean)很可能是更好的选择。

注意:可能令人觉得有趣的是,FilterToBeanProxy并没有任何特定用于Spring Security或者是保护Web应用程序的部分。在配置你自己的Servlet过滤器时,你可能会发现FilterToBeanProxy也很有用。实 际上,正是因为它如此有用,所以在1.2版本中,一个名为org.springframework.web.filter. DelegatingFilterProxy的类似的过滤器被添加到了Spring中。

最后,你将需要把那个过滤器与一个URL模式相关联。下面的<filter-mapping>将FilterToBeanProxy的这个Foo实例与/*的一个URL模式连接在一起,从而所有的请求都得以被处理:

  


不管你选择的是targetClass还是targetBean,FilterToBeanProxy都必须能够访问这个Spring应用 程序上下文。这意味着这个Spring上下文必须使用Spring的ContextLoaderListener或者是 ContextLoaderServlet进行加载(见第13章)。

既然已经知道FilterToBeanProxy是如何工作的了,那么目前最吸引人的问题就是:为什么所有这些工作都不得不和Spring Security一起完成呢?这个问题很有意义。

正如本书前面提到的那样,Spring Security在实施Web安全措施时使用Servlet过滤器。这些过滤器中的每一个都必须被注入来自Spring应用程序上下文的其他Bean,以 完成它们的工作。比如说,FilterSecurityInterceptor需要被注入AuthenticationManager和 AccessDecisionManager,那样它才能实施安全措施。不幸的是,Servlet规范并没有使得Servlet过滤器上的依赖注入工作容 易进行。FilterToBeanProxy通过充当在Spring应用程序上下文中被配置为bean的实际过滤器的“挂名人物”来解决这个问题。

 

 

7.4.1.2  代理多个过滤器

现在你可能正觉得奇怪,如果FilterToBeanProxy通过代理给一个Spring配置的Bean来处理请求,那么在接收端(即Spring端)是什么情况呢?那是一个极好的问题。

实际上,FilterToBeanProxy代理给的那个Bean可以是javax.servlet.Filter的任意实现。这可以是 Spring Security的任何一个过滤器,或者它可以是你自己创建的一个过滤器。但是正如本书已经提到的那样,Spring Security要求至少配置四个而且可能一打或者更多的过滤器。这是否意味着你必须为每一个Spring Security的过滤器配置一个FilterTo- BeanProxy呢?

当然不是。虽然肯定可以向web.xml文件添加数个FilterToBeanProxy(每个Spring Security过滤器一个),但是那样将会有特别多的XML要编写。为了使生活更加轻松一些,Spring Security提供了FilterToBeanProxy的同伴——FilterChainProxy。

FilterChainProxy是javax.servlet.Filter的一个实现,它可以被配置来同时把几个过滤器链接在一起,如图7.9所示。

 
(点击查看大图)图7.9  FilterChainProxy将多个过滤器链接在一起代表FilterToBeanProxy。

FilterBeanProxy拦截来自客户端的请求,然后把它发送给FilterChainProxy进行处理。 FilterChainProxy接着让那个请求通过一个或者是多个在Spring应用程序上下文中配置的过滤器。FilterChainProxy在 Spring应用程序上下文中像下面那样配置:

  
  

这里的filterInvocationDefinitionSource属性的值为一个String,它被解析为 FilterChainProxy将用来把过滤器链接到一起的一种模式。在这个示例中,第一行告诉FilterChainProxy在比较URL模式之 前,先把它们统一为小写字母。接下来一行说,在声明URL模式时,将使用Apache Ant样式路径。

最后,一个或多个“URL-到-过滤器-链”映像将被提供。在这里,/**模式(在Ant中,这意味着所有URL都将匹配)被映像给三个过滤器。被 配置为filter1 Bean的那个过滤器将是最外层的过滤器,并且将首先接收访问请求。接下来是filter2 Bean。而filter3 Bean将是最里层的Bean,并且将是在实际的受保护资源被处理之前接收访问请求的最后那个过滤器。在返回响应时,流程按逆序进行,从filter3到 filter1。

 

 

7.4.1.3  为Spring Security配置代理

到目前为止,我们介绍的过滤器代理配置主要都是一般性的。但是现在是该为在Spring Security的应用配置它们的时候了。首先,让我们在web.xml文件中配置一个FilterToBeanProxy:

  

在这里,我们把FilterToBeanProxy配置成代理给Spring上下文中任何类型为FilterChainProxy的Bean。这是最理想的,正如读者可能已经猜到的那样,因为我们正准备在Spring上下文中配置一个FilterChainProxy。

但是在离开这个web.xml文件之前,我们还需要为FilterToBeanProxy配置一个过滤器映像:

  

正如所有servlet <filter-mapping>一样,这里的<filter-name>值必须匹配正被映像的<filter> 的<filter-name>。至于这里的<url-pattern>,我们推荐使用/*,那样所有请求就都被输送经过 Spring Security,而且潜在地得到保护。尽管没有必要保护整个应用程序,但是通过/*过滤所有请求将保持web.xml配置的简单性。稍后,在配置 FilterSecurity- Interceptor时,我们可以选择应用程序的哪些部分应该得到保护,哪些不用进行保护。

而那就是在web.xml文件中所需做的全部工作!尽管Spring Security使用数个过滤器来保护一个Web应用程序,但我们只需在web.xml文件中配置这一个FilterToBeanProxy过滤器。从现 在起,我们将在Spring应用程序上下文中配置Spring Security。
说到Spring应用程序上下文,我们将需要一个 FilterChainProxy Bean来处理FilterToBeanProxy委托的请求。对于应用程序RoadRantz,让我们从最低限度的Spring Security配置开始,在Spring应用程序上下文中(也就是在roadrantz-security.xml文件中)与程序清单7.1所示 的<bean>声明一起配置一个FilterChainProxy。

程序清单7.1  为Spring Security配置一个FilterChainProxy

   

在这里,我们已配置FilterChainProxy把Spring Security的四个过滤器链接在一起。马上我们就会逐个研究这些过滤器。不过,首先需要指出的是,如果没有这样配置 FilterChainProxy,那么我们将不得不在web.xml文件中配置四个不同的<filter>入口和八个不同 的<filtermapping>入口。但是有了FilterChainProxy,web.xml配置就被简化为一 对<filter>和<filter-mapping>。

随着访问请求从客户端进来,然后向受保护的资源一路前进,它经过上述四个过滤器中的每一个。读者可以把Spring Security的过滤器想象为洋葱皮的不同层。图7.10显示了一个访问请求是如何在它前往某个受保护资源的路上经过每一个Spring Security过滤器的。

 
(点击查看大图)图7.10  Spring Security的过滤器各自成层以实施安全任务。


除 了看上去有一点点像动画片《华纳群星总动员》(Looney Tunes)的开场片段外 ,图7.10举例说明的是有关Spring Security的非常重要的内容。尽管并不是每一个应用程序都要求所有的Spring Security过滤器,但是至关重要的是,在FilterChainProxy的filter- InvocationDefinitionSource属性中,所使用的那些过滤器都必须按照一种特定的顺序进行配置。那是因为有些过滤器会假设在它们之 前已经完成了哪些安全任务。如果这些过滤器不按照次序进行配置,那么它们就会彼此冲突。

每一个在FilterChainProxy的filterInvocationDefinitionSource属性中配置的过滤器,都引用在 Spring应用程序上下文中配置的一个<bean>。下面让我们从集成过滤器开始,顺着访问请求经过每一个过滤器的路径走一遍。

 

 

7.4.2  处理安全上下文

你看过电影《海底总动员》(Finding Nemo)吗?如果看过,那么你肯定还记得其中的一个主要人物是名为多瑞的蓝色刺尾鱼。这部影片中许多逗人的瞬间都是多瑞与健忘症斗争的结果。在这部影片 中,她总会忘记一些小事,比如说他们要去哪儿或马林的名字,后者就是寻找他儿子尼莫的那条小丑鱼。

正如结果那样,HTTP和多瑞有很多共同之处。你知道,HTTP是一种没有国家之分的协议。那意味着,就像多瑞一样,HTTP生活在即时世界里,并 且趋向于忘记两次请求之间的事情。这对于保护通过HTTP提供的应用程序来说,就造成了一个小小的问题。由于没有什么东西帮助HTTP记住你是谁,因此你 将不得不为每一次请求登入一个应用程序。

幸运的是,已经有几个解决方案被设计来帮助HTTP治愈它的健忘症。对于基于Java的Web应用程序,会话对象可以被用来存储两次请求之间的数 据。对于每一个请求,已经存储的用户信息可以从相应的会话对象中检索得到,用来处理当前请求,然后放回到该会话对象中,那样下一个请求就可以使用它了。

一个请求必须经过的第一个Spring Security过滤器是HttpSessionContextIntegration Filter。这个过滤器的主要工作就是在两次请求之间尽力记住一个已经验证过身份的用户。它在Spring应用程序上下文中的配置情况如下所示:

 

当一个请求第一次进入时,HttpSessionContextIntegrationFilter会查看它是否能够在当前会话中找到相应用户的认 证信息(从上一个请求存储到那儿的)。如果找到了,那么HttpSessionContextIntegrationFilter接下来就会允许 Spring Security在当前请求期间使用这些用户信息。在当前请求结束时,HttpSessionContextIntegrationFilter将会把该 用户的认证信息存放回当前会话中,那样它们就可以在下次请求时使用了。

如果HttpSessionContextIntegrationFilter在当前会话中找到了一个用户的认证信息,那么该用户就不必再次登录 了。但是如果未能找到相应用户的认证信息,那么可能意味着他们尚未登录。为了处理用户登录,我们将需要配置一个认证处理过滤器,这是在 FilterChainProxy中接下来配置的一个过滤器,也是我们将讨论的下一个过滤器。

 

 

7.4.3  提示用户登录

在使用Spring Security保护Web应用程序时,身份验证是通过由一个认证入口点和一个认证处理过滤器组成的“双人组”(tag-team)完成的。如图7.11所示,一个认证入口点提示用户输入登录信息,然后由认证处理过滤器来处理那些信息。

  
图7.11  认证入口点和认证处理过滤器协同工作以验证
Web用户的身份。

一个认证入口点通过提示用户提供他们的凭证来启动这里的登录过程。在用户提交所要求的信息之后,一个认证处理过滤器就会尝试验证当前用户的身份。

Spring Security带有五对匹配的认证入口点和认证处理过滤器,如表7.5所示。

表7.5   Spring Security的认证入口点提示用户登录。
认证处理过滤器在用户凭证被提交后处理当前登录请求

认证入口点

认证处理器

   

BasicProcessingFilterEntryPoint

BasicProcessingFilter

利用 HTTP 基本身份验证,通过一个浏
览器对话框提示用户进行登录

AuthenticationProcessing
FilterEntryPoint

Authentication
ProcessingFilter

将当前用户重定向到一个基于 HTML
表单的登录页面

CasProcessingFilterEntryPoint

CasProcessingFilter

将当前用户重定向到由 JA-SIG CAS
单点登录解决方案提供的登录页面

DigestProcessingFilterEntryPoint

DigestProcessingFilter

利用 HTTP 摘要身份验证,通过一个
浏览器对话框提示用户进行登录

X509ProcessingFilterEntryPoint

X509ProcessingFilter

使用 X.509 证书处理身份验证


每一种完整过程。图11.8展示了这一协作过程。

认证入口点通过提示用户登录启动身份验证过程。当用户提交认证所需的信息,认证处理过滤器试图验证该用户的身份(在认证管理器的帮助下)。

让我们更进一步考察认证入口点和认证处理过滤器是如何协同工作来验证某一用户的身份的。我们将研究几个Spring Security认证选项,首先从Spring Security支持的HTTP基本身份验证开始。

 

 

 

7.4.3.1  基本身份验证

基于Web的各种身份验证方式中最简单的形式被称为基本身份验证。基本身份验证的工作机制是向Web浏览器发送一个HTTP 401(未授权)响应。在浏览器看到这个响应时,它就可以知道服务器需要这个用户进行登录。于是,浏览器弹出一个对话框来提示用户输入用户名和密码(如图 7.12所示)。

  
(点击查看大图)图7.12  HTTP基本身份验证使用一个浏
览器生成的登录对话框来提示用户输入他们的凭证。

这个对话框来自Mozilla“火狐”浏览器的Mac OS X版本。

在用户提交登录信息之后,浏览器把信息发送回服务器以进行身份验证。如果身份验证成功,那么用户就会被送往他所期望访问的目标URL。否则,服务器可能会发送回一个HTTP 401响应,而浏览器将会提示用户再次进行登录。

在Spring Security中使用基本身份验证,需要先配置一个BasicProcessingFilterEntry Point Bean:

 

BasicProcessingFilterEntryPoint只有一个属性需要配置。这个realmName属性指定一个任意字符串,该字符串 显示在登录对话框上,用于提示用户他们正被要求登入些什么内容。举例来说,图7.12中显示的对话框要求用户为进入RoadRantz领域而输入用户名和 密码。

在用户单击登录对话框中的“确定”按钮之后,输入的用户名和密码就会被通过HTTP头提交回服务器端。在那里,BasicProcessFilter将拾取并处理它:

 

BasicProcessingFilter从HTTP头中取得用户名和密码,然后把它们转交给通过authenticationManager属 性装配进来的认证管理器。如果身份验证成功,会将一个Authentication对象放到当前会话中供以后引用。否则,如果身份验证失败,控制就会被转 交给认证入口点(通过authenticationEntryPoint属性装配进来的BasicProcessingFilter EntryPoint),再给用户一次登录的机会。

尽管基本身份验证对于简单的应用程序而言很不错,但是它有一些局限性,主要是浏览器显示的登录对话框对用户而言既不友好,又不美观。当你希望有一个外观更加专业的登录界面时,基本身份验证就不能满足要求了。

对于前面提到的RoadRantz应用程序而言,我们希望能有一个吸引目光的Web页面,具有和该应用程序的其余部分相同的外观和质感。为 此,Spring Security的AuthenticationProcessingFilterEntryPoint更加适合我们的需求。下面让我们看看它是如何工作 的。

 

 

 

7.4.3.2  基于表单的身份验证

AuthenticationProcessingFilterEntryPoint是一个要求用户填写一张基于HTML的登录表单的认证入口点。 对于本章一直在引用的RoadRantz应用程序,我们将在roadrantz-security.xml文件中用以下方式来配置它:

 

AuthenticationProcessingFilterEntryPoint在这里配置了两个属性。其中,loginFromUrl属性被 设置为将显示登录页面的一个URL(相对于当前Web应用程序的上下文),forceHttps属性被设置为true,以促使登录页面通过HTTPS安全 地进行显示,即使原始的访问请求是通过HTTP发出的。

在这里,我们把loginFormUrl设置成了/login.htm。其实,loginFormUrl可以被配置成任何URL,用于把当前用户带 到一个HTML表单进行登录。在RoadRantz应用程序中,/login.htm归根结底与一个显示登录页面的Spring MVC UrlFilenameViewController相关联。图7.13显示的是RoadRantz登录页面可能的样子。

 
(点击查看大图)图7.13  RoadRantz登录页面位于/login.htm处,它归根结底
由Spring MVC的UrlFilenameViewController处理。


不管这个登录页面是如何显示的,重要的是它包含有一个如下所示的HTML表单:

 

这个登录表单中必须有两个名为j_username和j_password的域,供用户分别输入用户名和密码。那是因为只有它们才是 AuthenticationProcessingFilter所预期的域名称。至于表单的action属性,它被设置为 j_acegi_security_check,将会被AuthenticationProcessingFilter拦截住。

AuthenticationProcessingFilter是基于j_username和j_password参数提供的用户名和密码信息处理身份验证的过滤器。在roadrantz-security.xml文件中它是以如下方式配置的:

 

这里的filterProcessesUrl属性告诉AuthenticationProcessingFilter应该拦截哪一个URL。这个 URL与登录表单action属性中的URL是完全相同的。它的默认值为/j_acegi_security_check,但是这里明确定义了该值,目的 是说明你可以根据需要改变这个值。

这里的authenticationFailureUrl属性指定当身份验证失败时,用户应该被送往哪里。在这里,我们将把用户送回到登录页面,同时传递一个参数用于表明身份验证失败(因此在登录页面上可能会显示一个错误消息)。

在正常情况下,当身份验证成功后,AuthenticationProcessingFilter会在当前会话中放置一个 Authentication对象,并且将用户重新定向到他们所期望访问的页面。这里的defaultTargetUrl属性定义当出现目标URL不知在 何处的异常情况时将发生什么。如果用户直接导航到登录页面,而不是首先试图访问某一受保护的资源时,就可能发生这种情况。

最后,这里的authenticationManager属性被装配了一个指向authenticationManager Bean的引用。正如所有其他的身份验证处理过滤器一样,基于表单的Authentication- ProcessingFilter也依靠一个身份验证管理器来帮助确定当前用户的身份。

现在,我们已经有了一个在Spring配置中定义的身份验证处理过滤器和身份验证入口点,为用户登录做好了准备。但是还有一个遗留问题需要解决。这 里的身份验证处理过滤器被装配进了FilterChainProxy,但是你可能正想知道应该对这里的身份验证入口点做些什么。Spring Security的哪一部分使用这个身份验证入口点来提示用户进行登录呢?

本书很快便将回答那个问题。但是首先,我们将需要看一下例外转换过滤器——按顺序下一个处理受保护请求的过滤器。

 

 

7.4.4  处理安全例外

在提供保护的过程中,Spring Security过滤器中的任何一个都可能会抛出AuthenticationException或AccessDeniedException的某种变 体。举例来说,如果用户未能被认证(不管是任何原因),那么AuthenticationException就会被抛出。这可能是由于用户提供了一对无效 的用户名/密码,或者是可能甚至意味着用户根本就还没有尝试登录。即使用户成功地通过了身份验证,他们仍可能不被授予访问某些受保护页面所必需的权限。在 那种情况下,AccessDeniedException就会被抛出。

如果没有任何东西处理Spring Security的AuthenticationException或Access- DeniedException,它们就会流向Servlet容器,并且在浏览器中以一种实在难看的堆积样式显示在那儿。不用说,这不太理想。我们更喜欢 以一种更加优雅的方式来处理那样的例外情况。

那儿有ExceptionTranslationFilter要做的工作,它就在FilterSecurityInterceptor外面的那一层 配置,从而有机会捕获可能被FilterSecurityInterceptor抛出的那些例外情况。 ExceptionTranslationFilter在Spring中通过以下方式进行配置:

 

ExceptionTranslationFilter捕获FilterSecurityInterceptor抛出的例外……但是它对它们做些什么呢?

注意,ExceptionTranslationFilter被注入一个对身份验证入口点的引用。如果 ExceptionTranslationFilter捕获到一个AuthenticationException,那么它意味着当前用户还没有成功通过 身份验证。在那种情况下,该用户将被送到在authenticationEntryPoint属性中配置的身份验证入口点,以尝试登录。

 

 

7.4.4.1  处理身份验证例外

一个AccessDeniedException表明当前用户已经通过身份验证,但是尚未被授予足够访问所请求资源的权限。在那种情况下,一个 HTTP 403错误将被返回给当前浏览器。HTTP 403错误意味着“禁用”,即表明当前用户不被允许访问所请求的某一个资源。

在默认情况下,ExceptionTranslationFilter使用一个AccessDeniedHandlerImpl来处理 AccessDeniedExceptions。除非以另外的方式进行了配置,否则AccessDeniedHandlerImpl只向当前浏览器发送一 个HTTP 403错误。不幸的是,HTTP 403错误在浏览器中通常是以一种“对使用者不友善”(user unfriendly)的方式显示的。

不过,我们可以配置自己的AccessDeniedHandlerImpl,在AccessDeniedException被捕获到时,把当前用户 送往一个外观更友善的报错页面。下列XML将配置一个AccessDeniedHandlerImpl,把用户送到位于URL为/error.htm处的 报错页面:

 

现在,剩下要做的就是把这个accessDeniedHandler装配进ExceptionTranslationFilter中:

 

到目前为止,我们已经声明了Spring Security所要求的四个过滤器中的三个。迄今配置的这三个过滤器是Spring Security锁中的制动栓。现在该是配置FilterSecurity Interceptor(决定是否允许访问某一Web资源的锁舌)的时候了。

 

 

 

7.4.5  强制Web安全性

每当用户请求Web应用程序中的一个页面时,那个页面可能是需要保护的,也可能是不需要保护的。在Spring Security中,一个过滤器安全拦截器负责拦截请求,判断某一请求是否安全,并且给予身份验证和访问决策管理器一个

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics