《Spring实战》学习笔记-------保护方法应用


Spring Security提供了三种不同的安全注解:

  1. Spring Security自带的@Secured
  2. JSR-250的@RolesAllowed注解
  3. 表达式驱动的注解,@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter

按权限保护方法调用

@Secured和@RolesAllowed使用方法相同,不同的地方在于@RolesAllowed是Java标准中的,@Secured是Spring框架内的。

首先启用基于注解的方法安全性,通过继承GlobalMethodSecurityConfig类:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true)
//如果使用@RolesAllowed注解需要改成 (jsr250Enabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfig{
}

与之前Web安全认证类似,不同的是Web安全认证的权限判断是通过重写configure()方法对AuthenticationManagerBuilder参数来配置,这里是使用注解配置在目标方法上。

@Secured("ROLE_SPITTLE", "ROLE_ADMIN")
//或@RolesAllowed("ROLE_SPITTLE", "ROLE_ADMIN")
public void addSpitle(Spittle spittle);

@Secure和@RolesAllowed使用一个String数组作为参数。每个String为一个权限,调用这个方法的用户至少需要具备其中一种权限才能进行调用

这两种注解的弊端在于,只能通过权限来判断能否调用方法。


使用表达式保护方法调用

Spring Security提供了 4 个注解,可以使用SpEL表达式来保护方法调用:

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

首先需要将@EnableGlobalMethodSecurity注解的prePostEnabled属性设置为true,来启用它们:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{}

设定方法访问规则

@PreAuthorize和@PostAuthorize注解的区别在于表达式执行的时机,Pre在方法调用前,Post在方法调用后。

@PreAuthorize:

@PreAuthorize("hasRole('ROLE_SPITTLE')") //判断调用方法的用户是否有权限
@PreAuthorize("(hasRole('ROLE_SPITTLE') and #spittle.text.length() <= 140)"
				+ "or hasRole('ROLE_VIP')" )//普通用户的文字最多140字。VIP用户无限制。spittle能直接引用传入的同名参数
public void addSpittle(Spittle spittle);

@PostAuthorize:

@PostAuthorize("returnObject.spittle.username == principal.username")
//判断通过查询id返回的用户是不是当前认证的用户
public Spittle getSpittleById(Long id);

returnObject变量可以访问方法的返回对象,principal代表当前认证用户的主要信息,这两个由Spring Security提供。

如果SpEL表达式判定为false,将会抛出一个AccessDeniedException异常,而调用者也不会获得结果。

过滤方法的输入和输出

有时需要保护的并不是方法的调用,而是方法传入和输出的数据。例如,getOffensiveSpittles()方法会返回标记为具有攻击性的Spittle列表,这个方法主要提供给管理员使用。但是,普通用户也可以调用这个方法来查看自己的Spittle是不是具有攻击性。

@PostFilter:

@PreAuthorize("hasAnyRole({'ROLE_SPITTLE', 'ROLE_ADMIN'})")
@PostFilter("hasRole('ROLE_ADMIN') || "
			+ "filterObject.spittle.username == principal.username")//确保普通用户只能看到自己的spittle,如果返回的spittle的用户名和当前认证用户不同,就过滤掉它
public List<Spittle> getOffensiveSpittles();

除了事后过滤方法的返回值,还可以预先过滤传入到方法中的值,例如,批量删除Spittle,管理员能删除所有的,普通用户只能删除自己的Spittle

@PreAuthorize("hasAnyRole({'ROLE_SPITTLE', 'ROLE_ADMIN'})")
@ProFilter("hasRole('ROLE_ADMIN') || "
			+ "targetObject.spittle.username == principal.name")//遍历数组中的spittle,过滤掉其它用户的spittle
public void deleteSpittles(List<Spittle> spittles);

targetObject代表当前的传入参数。

定义许可证计算器

如果安全规则变得复杂,定义在注解中的SpEL表达式会变得笨重而且难以测试。可以将它改为:

@ProFilter("hasPermission(targetObject, 'delete')")

hasPermission()是扩展的一个属性,使用它需要配置一个实现PermissionEvaluator接口的类:

//这段代码抄自https://www.cnblogs.com/fenglan/p/5913463.html
public class MyPermissionEvaluator implements PermissionEvaluator {

   public boolean hasPermission(Authentication authentication,Object targetDomainObject, Object permission) {
      if ("user".equals(targetDomainObject)) {
         return this.hasPermission(authentication, permission);
      }
      return false;
   }

   //总是认为有权限
   public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
      return true;
   }

    //简单的字符串比较,相同则认为有权限
   private boolean hasPermission(Authentication authentication, Object permission) {
      Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
      for (GrantedAuthority authority : authorities) {
         if (authority.getAuthority().equals(permission)) {
            returntrue;
         }
      }
      return false;
   }
}

这个接口实现了两个hasPermission,第一个把要评估的对象作为第二个参数。第二个hasPermission只有在目标对象的ID可以得到时才有用,并将id作为Serializable传入第二个参数。

许可计算器已经准备好了,还需要将它注册到Spring Security中。我们要替换原有的表达式计算器,默认情况下,它使用DefaultMethodSecurityExpressionHandler。我们要提供另一个DefaultMethodSecurityExpressionHandler,让它使用自定义的MyPermissionEvaluator:

这需要重载GlobalMethodSecurityConfiguration的createExpressionHandler方法:

@Override
protected MethodSecurityExpresionHandler createExpressionHandler(){
	DefaultMethodSecurityExpressionHandler expresionHandler = new MyDefaultMethodSecurityExpressionHandler();
	expressionHandler.setPermissionEvaluator(new MyPermissionEvaluator);
	return expressionHandler;
}

这样,不管在任何地方的表达式使用hasPermission来保护方法,都会调用MyPermissionEvaluator来决定用户是否由权限调用方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值