`

Spring Security介绍(5)

阅读更多

 

 

7.5  视图层安全

在绝大多数应用程序中,都有一些只应该显示给某一类用户的元素。正如读者已经看到的那样,Spring Security的过滤器可以阻止某些页面呈现给没有被授予特定权限集的用户。

但是,过滤器提供的是一种比较粗鲁的安全措施,它们在请求级上限制访问。在某些情况下,你可能希望更优雅地控制当前用户允许被查看的内容。可能一个应用程序的所有用户都被允许查看某一页面,但是只有被授予特定权限的用户才能看到该页面上的某些元素。

为了在Web应用程序中提供优雅的安全措施,Spring Security带有一个小型(但是功能强大)的JSP标签库。这个标签库只提供了三个标签,如表7.6所列。

表7.6   Spring Security用于视图层安全的JSP标签

   

<authz:acl>

根据当前用户是否已被授予针对某一域对象的一组特定权限之一,
有条件地渲染标签体

<authz:authentication>

提供有关当前用户的信息

<authz:authorize>

根据当前用户是否已被授予某些权限,有条件地渲染标签体

要想在一个JSP页面中使用这些标签,必须利用JSP的<%@taglib% >指令来引入标签库:

 

 

7.5.1  有条件地渲染内容

在Spring Security的JSP标签中,最有用的是<authz:authorize>标签。这个标签可以有效执行一个if语句,评估当前用户是否已被授予查看某些内容的适当权限。如果是,标签体的内容就会被渲染。否则,这个标签的内容就会被忽略。

为了便于把问题讲清楚,让我们添加一个欢迎消息以及用来注销RoadRantz应用程序的一个链接。欢迎一个没有通过身份验证的用户没有多少意义, 而为他们提供一个注销链接就更没意义了。因此,我们希望能够确定在把该信息呈现给用户之前,相应的用户已经被授予某些权限。利 用<authz:authorize>标签的ifAllGranted属性,我们可以使用下面这个JSP代码片断把标签内容添加到视图中:

由于这里使用了ifAllGranted属性,因此,包含在标签体中的内容将只在驾车者已被授予 ROLE_MOTORIST和ROLE_VIP权限时渲染。不过,那样限制得太死了,因为虽然所有用户均被授予ROLE_MOTORIST权限,但是只有 极少数被授予了ROLE_VIP权限。因此,可能<authz:authorize>标签的ifAnyGranted属性会更适当一些:

 

在这里,要想显示上述欢迎消息和注销链接,用户必须被授予ROLE_MOTORIST或ROLE_VIP权限。


尽管可能看上去很明显,但是还是值得指出,如果只是检查一个权限,那么在ifAllGranted和ifAnyGranted之间进行选择就没有什么实际意义。当属性值中只列有一个权限时,这两个属性起到的作用将完全相同。

可以选择的最后一个属性是ifNotGranted,它只在当前用户没有被授予所列出的任一权限时渲染标签的内容。举例来说,可以使用这个属性来阻止内容被渲染给匿名用户:

 

这三个属性本身覆盖许多范围。但是,当它们一起使用时,可以出现一些真实的安全魔法。在一起使用时,通过把这些属性逻辑“与”在一起进行求值。例如,考虑下列代码:

 


这样一起使用,这里的标签内容将只在当前用户已被授予ROLE_MOTORIST权限以及ROLE_VIP或ROLE_FAST_LANE 权限,同时没有被授予ROLE_ADMIN权限时被渲染。尽管这是一个精心设计的示例,但是读者应该可以想像到,在组 合<authz:authorize>标签的三个属性之后,其功能可以变得多强大。

控制当前用户可以看到些什么,这只是Spring Security的JSP标签库的一个方面。下面让我们一起看一下如何利用Spring Security标签来显示有关某一已通过身份验证的用户的信息。

 

 

7.5.2  显示用户身份验证信息

在上一小节中,我们给RoadRantz应用程序为通过身份验证的用户添加了一个欢迎消息。为简单起见,该消息被设置为“Welcome Motorist!”。那是一个好的开端,但是我们希望通过显示当前用户的登录名而不是“Motorist”,来使得该应用程序更具个性化。

幸运的是,当前用户的登录信息通常在从该用户的Authentication.getPrincipal()方法返回的那个对象中。我们需要的只是一种在JSP中访问该重要对象的便利方法。那就是<authz:authentication>标签的目的所在。

<authz:authentication>标签把从Authentication.getPrincipal()返回的那个对象的 属性呈递给JSP输出。Authentication.getPrincipal()通常返回Spring Security的org.acegisecurity. userdetails.UserDetails接口的一个实现,包括一个getUsername()方法。因此,要想显示UserDetails对象的 username属性,我们需要做的只是添加下列<authz:authentication>标签:

  


这里的operation属性有一点使人迷惑,似乎表明它的目的是调用一个方法。确实,它调用一个方法,但是更加特别的是,它调用在operation属性中指定名称的那个属性的getter方法。

在默认情况下,这个operation属性值的第一个字母将被大写,而且其结果被预先与get一同考虑,以生成将被调用的那个方法的名称。在这里,getUsername()方法被调用,同时它的返回值被呈递给JSP输出。

现在,读者已经了解如何利用Spring Security的过滤器来保护Web应用程序。不过,在结束Spring Security之前,让我们快速看一下如何利用Spring Security和AOP保护方法调用。

 

 

7.6  保护方法调用

虽然Spring Security保护Web请求的手段是使用Servlet过滤器,它却是利用Spring对AOP的支持来提供方法级别的声明式保护的。这意味着不是设 置一个SecurityEnforcementFilter来强制安全性,而是设置一个Spring AOP代理来拦截方法调用,并将控制转交给一个安全拦截器。

7.6.1  创建一个安全切面

也许设置一个AOP代理的最简单的方式是使用Spring的BeanNameAutoProxyCreator,并且简单地列举出你想要保护的Bean 。例如,假设你希望保护courseService和billingService Bean:

 

在这个示例中,我们要求自动代理创建器通过一个名为securityInterceptor的拦截器代理它的Bean。这个securityInterceptor Bean按照以下方式配置:

 
  

MethodSecurityInterceptor针对方法调用所做的一切与FilterSecurityInterceptor针对Servlet请求所做的是相同的。更确切的说,它拦截调用,并协调认证管理器和访问决策管理器的工作,以确保方法需求得到满足。

注意,这里的authenticationManager和accessDecisionManager属性与 FilterSecurityInterceptor的情况是完全相同的。事实上,可以像对于FilterSecurityInterceptor那样把 那些相同的Bean装配到这些属性中。

MethodSecurityInterceptor也和FilterSecurityInterceptor一样有一个 objectDefinitionSource属性。不过,尽管它在这里和FilterSecurityInterceptor的同名属性的用途是完全相 同的,但是在配置方式上还是有些许不同。它不是将URL模式与权限相关联,而是将方法模式与调用该方法所需要的权限进行关联。

一个方法模式(如图7.15所示)包含完全匹配类名和需要保护的方法的名字。值得注意的是,你可以在方法模式的开头或结尾处使用通配符以匹配多个方法。

 
(点击查看大图)图7.15  方法安全规则通过把一个完全匹配
类名和方法映射到执行该方法所要求的权限来进行定义。
在指定相应的方法时可以使用通配符。


当一个受保护的方法被调用时,MethodSecurityInterceptor将判断当前用户是否已通过身份验证并且拥有合适的授权来 调用该方法。如果是,则会继续调用目标方法。如果不是,则会抛出一个AcegiSecurityException。更具体地说,如果当前用户身份无法得 到验证,将会抛出一个AuthenticationException。或者,如果当前用户没有获得足够权限调用本方法,则会被抛出一个 AccessDeniedException。

与Spring的例外基本原理一致,AcegiSecurityException是一个未检查的例外。进行调用的代码可以选择捕获或者忽略该例外。

在Spring配置文件中编写方法安全属性仅仅是声明方法级别安全性的一种手段。现在让我们一起看一下如何使用Jakarta Commons Attributes来声明安全属性。

 

 

7.6.2  使用元数据保护方法

与事务和处理器映射机制相同,你首先必须要做的是声明一个元数据实现以告诉Spring如何装载元数据。如果还没有给你的应用程序上下文增加一个CommonsAttributes Bean,现在就添加一个:

 

接下来,你必须要声明一个对象定义源。在7.6.1小节中,我们通过将 objectDefinition Source属性设置为一个将安全属性映射到方法的字符串定义了一个对象定义源。而现在我们将在被保护对象的源代码中直接声明安全属性。Spring Security的MethodDefinition Attributes是一个对象定义源,它从受保护对象的元数据中获取它的安全属性:

 

MethodDefinitionAttributes的attributes属性被装配了一个指向attributes bean的引用,因此它能够知道如何使用Jakarta Commons Attributes来控制安全属性。

现在,属性objectDefinitionSource已经配置好了,我们把它装配到MethodSecurityInterceptor的objectDefinitionSource属性中(代替在7.6.1小节中的String定义):

 

现在,可以开始为你的代码加上安全属性标记了。你唯一需要知道的安全属性是 SecurityConfig,它在权限和方法之间建立关联。举例来说,以下的代码段展示了如何标记CourseService的 enrollStudentInCourse()方法,声明它需要ROLE_ADMIN或者是ROLE_REGISTRAR权限:

 


在enrollStudentInCourse()方法上声明这些安全属性和在7.6.1小节中定义的objectDefinitionSource声明是等价的。

 

 

7.7  小结

安全性对于许多应用程序而言都是非常重要的方面。Spring Security提供了一种基于Spring的松弛耦合、依赖注入和面向切面编程基本原理的机制来保护用户的应用程序。

读者可能已经注意到了,本章只提供了非常少的Java代码。希望读者不会因此而感到失望。Java代码的缺少阐明了Spring Security的一个关键强项——应用程序与其安全性之间的松弛耦合。安全性是一个胜过应用程序核心关注点的切面。使用Spring Security,无需直接在你的应用程序代码中编写任何安全代码,即可达到保护应用系统的目的。

另一件读者可能已经注意到的事是,大多数使用Spring Security保护某一应用程序所需的配置都并不需要知道它所保护的应用程序本身。唯一真正需要知道被保护应用程序所有细节的Spring Security组件是用于关联被保护资源和访问该资源所需权限的对象定义源。在Spring Security和它的应用系统之间,双向都是松弛耦合。

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics