Spring Security 保护方法调用

一、使用注解保护方法

在Spring Security中实现方法级安全性的最常见办法是使用特定的注解,将这些注解应用到需要保护的方法上。这样有几个好处,最重要的是当我们在编辑器中查看给定的方法时,能够很清楚地看到它的安全规则。
Spring Security提供了三种不同的安全注解:

  • Spring Security自带的@Secured注解;
  • JSR-250的@RolesAllowed注解;
  • 表达式驱动的注解,包括@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。

@Secured和@RolesAllowed方案非常类似,能够基于用户所授予的权限限制对方法的访问。当我们需要在方法上定义更灵活的安全规则时,Spring Security提供了@PreAuthorize和@PostAuthorize,而@PreFilter/@PostFilter能够过滤方法返回的以及传入方法的集合。

1.1 使用@Secured注解限制方法调用

在Spring中,如果要启用基于注解的方法安全性,关键之处在于要在配置类上使用@EnableGlobalMethodSecurity,如下所示:
Spring Security 保护方法调用_第1张图片
让我们回到@EnableGlobalMethodSecurity注解,注意它的securedEnabled属性设置成了true。如果securedEnabled属性的值为true的话,将会创建一个切点,这样的话Spring Security切面就会包装带有@Secured注解的方法。例如,考虑如下这个带有@Secured注解的addSpittle()方法:
Spring Security 保护方法调用_第2张图片
@Secured注解会使用一个String数组作为参数。每个String值是一个权限,调用这个方法至少需要具备其中的一个权限。通过传递进来ROLE_SPITTER,我们告诉Spring Security只允许具有ROLE_SPITTER权限的认证用户才能调用addSpittle ()方法。

如果传递给@Secured多个权限值,认证用户必须至少具备其中的一个才能进行方法的调用。例如,下面使用@Secured的方式表明用户必须具备ROLE_SPITTER或ROLE_ADMIN权限才能触发这个方法:
Spring Security 保护方法调用_第3张图片
如果方法被没有认证的用户或没有所需权限的用户调用,保护这个方法的切面将抛出一个Spring Security异常(可能是AuthenticationException或AccessDeniedException的子类)。它们是非检查型异常,但这个异常最终必须要被捕获和处理。如果被保护的方法是在Web请求中调用的,这个异常会被SpringSecurity的过滤器自动处理。否则的话,你需要编写代码来处理这个异常。

1.2 在Spring Security中使用JSR-250的@RolesAllowed注解

@RolesAllowed注解和@Secured注解在各个方面基本上都是一致的。唯一显著的区别在于@RolesAllowed是JSR-250定义的Java标准注解。

如果选择使用@RolesAllowed的话,需要将@EnableGlobalMethodSecurity的jsr250Enabled属性设置为true,以开启此功能:
Spring Security 保护方法调用_第4张图片
jsr250Enabled,与securedEnabled并不冲突。这两种注解风格可以同时启用。
@RolesAllowed注解和@Secured注解在使用上是一样的:
Spring Security 保护方法调用_第5张图片

二、使用表达式实现方法级别的安全性

Spring Security 3.0引入了几个新注解,它们使用SpEL能够在方法调用上实现更有意思的安全性约束。

2.1 注解

注解 描述
@PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问
@PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
@PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
@PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

2.2 启用

我们需要将@EnableGlobalMethodSecurity注解的prePostEnabled属性设置为true,从而启用它们:

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;

@Configurable
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
}

2.3 表述方法访问规则

1) @PreAuthorize和@PostAuthorize

@PreAuthorize和@PostAuthorize,它们能够基于表达式的计算结果来限制方法的访问。在定义安全限制方面,表达式带了极大的灵活性。通过使用表达式,只要我们能够想象得到,就可以定义任意允许访问或不允许访问方法的条件。

2) @PreAuthorize和@PostAuthorize之间的关键区别

@PreAuthorize和@PostAuthorize之间的关键区别在于表达式
执行的时机。@PreAuthorize的表达式会在方法调用之前执行,如
果表达式的计算结果不为true的话,将会阻止方法执行。与之相
反,@PostAuthorize的表达式直到方法返回才会执行,然后决定
是否抛出安全性的异常。

3) 在方法调用前验证权限

@PreAuthorize乍看起来可能只是添加了SpEL支持的@Secured和@RolesAllowed。实际上,你可以基于用户所授予的角色,使用@PreAuthorize来限制访问:
Spring Security 保护方法调用_第6张图片

@PreAuthorize的String类型参数是一个SpEL表达式。借助于SpEL表达式来实现访问决策,我们能够编写出更高级的安全性约束。例如,Spittr应用程序的一般用户只能写140个字以内的Spittle,而付费用户不限制字数。
Spring Security 保护方法调用_第7张图片

4) 在方法调用之后验证权限

在方法调用之后验证权限并不是比较常见的方式。事后验证一般需要基于安全保护方法的返回值来进行安全性决策。这种情况意味着方法必须被调用执行并且得到了返回值。

例如,假设我们想对getSpittleById()方法进行保护,确保返回的Spittle对象属于当前的认证用户。我们只有得到Spittle对象之后,才能判断它是否属于当前用户。因此,getSpittleById()方法必须要先执行。在得到Spittle之后,如果它不属于当前用户的话,将会抛出安全性异常。

除了验证的时机之外,@PostAuthorize与@PreAuthorize的工作方式差不多,只不过它会在方法执行之后,才会应用安全规则。此时,它才有机会在做出安全决策时,考虑到返回值的因素。

例如,要保护上面描述的getSpittleById()方法,我们可以按照如下的方式使用@PostAuthorize注解:
在这里插入图片描述
为了便利地访问受保护方法的返回对象,Spring Security在SpEL中提供了名为returnObject的变量。在这里,我们知道返回对象是一个Spittle对象,所以这个表达式可以直接访问其spittle属性中的username属性。

在对比表达式双等号的另一侧,表达式到内置的principal对象中取出其username属性。principal是另一个Spring Security内置的特殊名称,它代表了当前认证用户的主要信息(通常是用户名)。

在Spittle对象所包含Spitter中,如果username属性与principal的username属性相同,这个Spittle将返回给调用者。否则,会抛出一个AccessDeniedException异常,而调用者也不会得到Spittle对象。

有一点需要注意,不像@PreAuthorize注解所标注的方法那样,@PostAuthorize注解的方法会首先执行然后被拦截。这意味着,你需要小心以保证如果验证失败的话不会有一些负面的结果。

2.4 过滤方法的输入和输出

1) @PostFilter

@PostFilter事后对方法的返回值进行过滤
与@PreAuthorize和@PostAuthorize类似,@PostFilter也使用一个SpEL作为值参数。但是,这个表达式不是用来限制方法访问的,@PostFilter会使用这个表达式计算该方法所返回集合的每个成员,将计算结果为false的成员移除掉。
Spring Security 保护方法调用_第8张图片

2)@PostFilter

@PostFilter用于事先对方法的参数进行过滤。
与@PostFilter非常类似,@PreFilter也使用SpEL来过滤集合,只有满足SpEL表达式的元素才会留在集合中。但是它所过滤的不是方法的返回值,@PreFilter过滤的是要进入方法中的集合成员。
Spring Security 保护方法调用_第9张图片
targetObject是Spring Security提供的另外一个值,它代表了要进行计算的当前列表元素。

2.5 许可计算器

1) 定义许可计算器

定义许可计算器需要继承SpittlePermissionEvaluator类:
Spring Security 保护方法调用_第10张图片
SpittlePermissionEvaluator实现了Spring Security的PermissionEvaluator接口,它需要实现两个不同的hasPermission()方法。其中的一个hasPermission()方法把要评估的对象作为第二个参数。第二个hasPermission()方法在只有目标对象的ID可以得到的时候才有用,并将ID作为Serializable传入第二个参数。

2) 注册许可计算器到Spring Security

默认情况下,Spring Security会配置为使用DefaultMethodSecurityExpression-Handler,它会使用一个DenyAllPermissionEvaluator实例。顾名思义,Deny-AllPermissionEvaluator将会在hasPermission()方法中始终返回false,拒绝所有的方法访问。但是,我们可以为Spring
Security提供另外一个DefaultMethod-SecurityExpressionHandler,让它使用我们自定义的SpittlePermissionEvaluator,这需要重载GlobalMethodSecurityConfiguration的createExpressionHandler方法:
Spring Security 保护方法调用_第11张图片
3) 使用许可计算器
Spring Security 保护方法调用_第12张图片

你可能感兴趣的:(Java)