基于角色标准投票机制的标准实现是使用 RoleVoter ,还有一种替代方法可用来定义语法复杂的投票规则即使用Spring 表达式语言( SpEL )。要实现这一功能的直接方式是在 <http> 配置元素上添加 use-expressions 属性:
<http auto-config="true" use-expressions="true">
添加后将要修改用来进行拦截器规则声明的 access 属性,改为 SpEL 表达式。 SpEL 允许使用特定的访问控制规则表达式语言。与简单的字符串如 ROLE_USER 不同,配置文件可以指明表达式语言触发方法调用、引用系统属性、计算机值等等。
SpEL 的语法与其他的表达式语言很类似,如在 Tapestry 等框架中用到的 Object Graph Notation Language (OGNL) ,以及用于 JSP 和 JSF 的 Unified Expression Language 。它的语法面很广,已经超出了本书的覆盖范围,我们将会通过几个例子为你构建表达式提供一些确切的帮助。
需要注意的重要一点是,如果你通过使用 use-expressions 属性启用了 SpEL 表达式访问控制,将会使得自动配置的 RoleVoter 实效,后者能够使用角色的声明,正如在前面的例子所见到的那样:
<intercept-url pattern="/*" access="ROLE_USER"/>
这意味着如果你仅仅想通过角色来过滤请求的话,访问控制声明必要要进行修改。幸运的的是,这已经被充分考虑过了,一个 SpEL 绑定的方法 hasRole 能够检查角色。如果我们要使用表达式来重写例子的配置,它可能看起来如下所示:
<http auto-config="true" use-expressions="true"> <intercept-url pattern="/*" access="hasRole('ROLE_USER')"/> </http>
正如你可能预料的那样, SpEL 使用了一个不同的 Voter 实现类,即o.s.s.web.access.expression.WebExpressionVoter ,它能理解怎样解析 SpEL 表达式。 WebExpressionVoter 借助于 o.s.s.web.access.expression.WebSecurityExpressionHandler 接口的一个实现类来达到这个目的。
WebSecurityExpressionHandler 同时负责评估表达式的执行结果以及提供在表达式中应用的安全相关的方法。这个接口的默认实现对外暴露了 o.s.s.web.access.expression.WebSecurityExpressionRoot 类中定义的方法。
这些类的流程以及关系如下图所示:
为实现 SpEL 访问控制表达式的方法和伪属性( pseudo-property )在类 WebSecurityExpessionRoot 及其父类的公共方法中进行了声明。
【伪属性( pseudo-property )是指没有传入参数并符合 JavaBeans 的 getters 命名格式的方法。这允许 SpEL表达式能够省略方法的圆括号以及 is 或 get 的前缀。如 isAnonymous() 方法可以通过 anonymous 伪属性来访问。】
Spring Security 3 提供的 SpEL 方法和伪属性在以下的表格中进行了描述。要注意的是没有被标明“ web only ”的方法和属性可以在保护其他类型的资源中使用,如在保护方法调用时。示例表示的方法和属性是使用在<intercept-url> 的 access 声明中。
方法 | web only? | 描述 | 示例 |
hasIpAddress(ipAddress) |
Y | 用于匹配一个请求的 IP 地址或一个地址的网络掩码 | 1、access="hasIpAddress('162.79.8.30')" 2、access="hasIpAddress('162.0.0.0/224')" |
hasRole(role) | N | 用于匹配一个使用GrantedAuthority 的角色(类似于 RoleVoter ) | access="hasRole('ROLE USER')" |
hasAnyRole(role) | N | 用于匹配一个使用GrantedAuthority 的角色列表。用户匹配其中的任何一个均可放行。 | access="hasRole('ROLE_ USER','ROLE_ADMIN')" |
除了以上表格中的方法,在 SpEL 表达式中还有一系列的方法可以作为属性。它们不需要圆括号或方法参数。
属性 | web only? | 描述 | 示例 |
permitAll | N | 任何用户均可访问 | access="permitAll" |
denyAll | N | 任何用户均不可访问 | access="denyAll" |
anonymous | N | 匿名用户可访问 | access="anonymous" |
authenticated | N | 检查用户是否认证过 | access="authenticated" |
rememberMe | N | 检查用户是否通过remember me 功能认证的 | access="rememberMe" |
fullyAuthenticated | N | 检查用户是否通过提供完整的凭证信息来认证的 | access="fullyAuthenticated" |
需要记住的是, voter 的实现类必须基于请求的上下文返回一个投票的结果(允许、拒绝或者弃权)。你可能会认为 hasRole 会返回一个 Boolean 值,实际上正是如此。基于 SpEL 的访问控制声明必须是返回 Boolean 类型的表达式。返回值为 true 意味着投票器的结果是允许访问, false 的结果意味着投票器拒绝访问。
【如果一个表达式的值不是 Boolean 类型的,你将会得到如下的一个异常信息:org.springframework.expression.spel.SpelException:
EL1001E:Type conversion problem, cannot convert from
class java.lang.Integer to java.lang.Boolean 】
另外,表达式不能返回一个弃权类型的结果,除非访问控制声明不是一个合法 SpEL 表达式,在这种情况下投票器将会放弃投票。
如果你不在乎这些细小的约束, SpEL 访问控制声明能够提供一种灵活的配置访问控制决策的方式。
在本章中,我们提供了安全领域两个重要概念即认证和授权的介绍。
在总体上了解我们要进行安全保护的系统;
使用 Spring Security 的自动配置在三步之内实现了我们应用的安全配置;
了解了在 Spring Security 中 servlet 过滤器的使用及重要性;
了解了认证和授权过程中重要的角色,包括一些重要类实现的详细介绍如 Authentication 和 UserDetails
体验了与访问控制规则有关的 SpEL 表达式的配置。
在接下来的一章中,我们将通过添加一些增强用户体验的功能,把基于用户名和密码的认证提高一个新的水平。