Chapter 15. 基于表达式的权限控制

Spring Security 3.0介绍了使用Spring EL表达式的能力,作为一种验证机制 添加简单的配置属性的使用和访问决策投票,就像以前一样。 基于表达式的安全控制是建立在相同架构下的,但是允许使用复杂的布尔逻辑 包含在单独的表达式中。

15.1. 概述

Spring Security使用Spring El来支持表达式,你应该看一下如何工作的 如果你对深入了解这个话题感兴趣的话。表达式是执行在一个根对象 上的,作为一部分执行上下文。Spring Security使用特定的类对应web和方法安全,作为根对象, 为了提供内建的表达式,访问值,比如当前的认证主体。

15.1.1. 常用内建表达式

表达式根对象的基本类是SecurityExpressionRoot。 这个提供了一些常用的表达式,都可以在web和method权限控制中使用。

Table 15.1. 常用内建表达式

表达式说明
hasRole([role])返回 true 如果当前主体拥有特定角色。
hasAnyRole([role1,role2])返回 true 如果当前主体拥有任何一个提供的角色 (使用逗号分隔的字符串队列)
principal允许直接访问主体对象,表示当前用户
authentication允许直接访问当前 Authentication对象 从SecurityContext中获得
permitAll一直返回true
denyAll一直返回false
isAnonymous() 如果用户是一个匿名登录的用户 就会返回 true
isRememberMe() 如果用户是通过remember-me 登录的用户 就会返回 true
isAuthenticated()如果用户不是匿名用户就会返回true
isFullyAuthenticated()如果用户不是通过匿名也不是通过remember-me登录的用户时, 就会返回true

15.2. Web 安全表达式

为了保护单独的URL,你需要首先把<http>use-expressions属性设置为true。Spring Security会把 <intercept-url>access当做包含了Spring EL表达式。 表达式应该返回boolean,定义访问是否应该被允许,比如:

  <http use-expressions="true">
    <intercept-url pattern="/admin*"
        access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
    ...
  </http>

这里我们已经定义了应用的admin范围(通过URL模式定义的) 应该只对拥有admin权限的用户有效,并且用户要在本地子网的IP地址下。 我们已经看到了内建的hasRole表达式,在上一章节。 表达式hasIpAddress是一个附加的内建表达式,是由web安全提供的。 它是由WebSecurityExpressionRoot定义的,它的实例作为表达式根对象 当执行web权限表达式时。这个对象也直接暴露了HttpServletRequest对象 使用request这个名字,所以你可以直接调用request在一个表达式里。

如果使用了表达式,一个WebExpressionVoter会被添加到 AccessDecisionManager中,这是被命名空间创建的。 所以如果你没有使用命名空间还希望使用表达式,你必须把这些添加到你的配置中。

15.3. 方法安全表达式

方法安全有一点儿复杂,比单独允许和拒绝规则来说。Spring Security 3.0介绍了一些新注解, 为了对复杂的表达式进行支持。

15.3.1. @Pre@Post 注解

这里有四个注解,支持表达式属性允许进行前置和后置调用验证检测,也支持过滤提交的 集合参数或返回值。它们是@PreAuthorize, @PreFilter, @PostAuthorize@PostFilter。它们可以通过 global-method-security 命名空间元素启用:

<global-method-security pre-post-annotations="enabled"/>

15.3.1.1. 访问控制使用 @PreAuthorize@PostAuthorize

使用最广泛的注解是@PreAuthorize,它可以决定一个方法是否可以被调用。 比如(来自Contacts实例应用)

  @PreAuthorize("hasRole('ROLE_USER')")
  public void create(Contact contact);

这意味着只允许拥有"ROLE_USER"角色的用户访问。 很明显,相同的事情可以简单实用传统的配置方法,简单的配置属性来要求角色。 但是如果是这样呢:

  @PreAuthorize("hasPermission(#contact, 'admin')")
  public void deletePermission(Contact contact, Sid recipient, Permission permission);

这里我们其实使用了方法参数作为表达式的一部分来决定是否当前的用户拥有admin 表达式对给定的contract。内建的 hasPermission()表达式链接到 Spring Security ACL模块,通过applicaton context。你可以访问任何方法参数,通过名称 就像表达式的变量,在编译的时候使用debug信息。任何Spring EL功能都可以在表达式里使用, 所以你可以访问参数的属性。比如,如果你希望特定的方法只允许访问一个用户名和contract匹配, 你可以写成

  @PreAuthorize("#contact.name == authentication.name)")
  public void doSomething(Contact contact);

这里我们访问了另一个内建的表达式,authentication, 它是保存在安全上下文里的Authentication实例。 你也可以直接访问它的principal属性, 使用principal表达式。这个值一般是一个 UserDetails实例,所以你可能使用一个 像是principal.username或是 principal.enabled的表达式。

不太常见的是,你可能希望之星一个访问控制检测,在方法调用之后。这个可以使用 @PostAuthorize 注解。为了访问一个方法的返回值, 使用表达式中的内建名称returnObject

15.3.1.2. 过滤使用 @PreFilter@PostFilter

你可能已经知道了,Spring Security支持对集合和数据的过滤, 现在也可以使用表达式实现了。通常是用在一个方法的返回值中。比如:

  @PreAuthorize("hasRole('ROLE_USER')")
  @PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
  public List<Contact> getAll();

当使用@PostFilter注解时,Spring Security遍历返回的集合 删除任何表达式返回false的元素。名字filterObject引用 集合中的当前对象。你也可以在方法调用之前进行过滤,使用@PreFilter 虽然很少需要这样做。这个语法是一样的,但是如果这里有更多参数,都是集合类型 然后你必须使用注解的filterTarget属性选择其中一个。

注意过滤显然不是一个让你读取数据查询的解决方法。如果你过滤大型集合,删除很多实体, 然后这就会是非常没有效率的。