我们前面都是使用@PreAuthorize注解,然后在在其中使用的是hasAuthority方法进行校验。Spring Security还为我们提供了其它方法.
例如:hasAnyAuthority,hasRole,hasAnyRole,等。
这里我们先不急着去介绍这些方法,我们先去理解hasAuthority的原理,然后再去学习其他方法就会更容易理解。并且我们也可以选择定义校验方法,实现我们自己的校验逻辑。
hasAuthority方法实际是执行到了SecurityExpressionRoot的hasAuthority,大家只要断点调试既可知道它内部的校验原理。
它内部其实是调用authentication的getAuthorities方法获取用户的权限列表。然后判断我们存入的方法参数数据在权限列表中。
在 SecurityExpressionRoot 这个类的内部可以看到hasAuthority方法是怎么实现的。
这个方法的参数authority实际上就是 @PreAuthorize(“hasAuthority(‘test’)”) 这个注解中的 ‘test’ 。可以注意的是,在hasAuthority方法的内部又调用了一个方法 hasAnyAuthority 。
这个方法接收了一个字符串的可变参,也就是说它本身可以接收多个字符串的参数,可见它的本质还是一个数组。
同时在 hasAnyAuthority 方法内部 又调用了 hasAnyAuthorityName方法。但是我们并不用关心它在内部的调用链路,只需要关注核心的实现代码就可以了。
在 hasAnyAuthorityName 这个方法里面有两个参数,一个参数是prefix,在hasAnyAuthority方法中传进来的时候是null。第二个参数roles也就是之前的authorities传教来的参数。
在第二行使用了一个set集合、泛型是String的变量,在创建的时候调用了方法getAuthoritySet (获取权限集合)。
观察 getAuthoritySet 方法 ,发现它的执行流程是首先判断roles是否为空,如果为空就走到if的里面去,而接下来的一条语句就是核心代码。这里接收了 this.authentication ,这个就是我们之前在filter中存入到holder当中的对象,即UsernamePasswordAuthenticationToken,可以知道这里userAuthorities拿到了我们的权限对象,最终就调用到了我们自己在LoginUser类中重写的方法里去。
然后又将我们的数据进行了处理,把list集合转化为了set集合。
最终返回的set集合,是一个String类型的。
这个set集合被hasAnyAuthorityName 方法拿到,而这个权限信息其实就是当前用户所具有的权限信息。然后判断当前用户有没有访问接口的权限,hasAnyAuthorityName 方法的roles参数就是访问接口所需要具有的权限。就是拿roles这个元素到getAuthoritySet 判断是否存在。
接着在hasAnyAuthorityName 方法中使用for循环遍历参数。在for循环中拿着role去执行了方法 getRoleWithDefaultPrefix ,这个方法在内部对字符串做了一个拼接。它会拿一个前缀和我们的权限字符串做一个拼接。
但是回到hasAnyAuthorityName 方法中,传进去的prefix前缀的值实际上是null,也就是说实际上并没有做一个拼接,没有前缀,最终的结果也就是原本的字符串。如果有权限就会返回一个true,没有权限就会返回一个false。
从上面的源码中我们可以发现,hasAnyAuthority方法实际上可以传入多个权限,只只要用户有其中任意一个权限都可以访问对应资源。
传入多个权限的写法:
//标注在controller层
@PreAuthorize("hasAuthority('test','admin')")
hasRole要求有对应的角色才可以访问,但是它内部会把我们传入的参数拼接上ROLE_后再去比较。所以这种情况下要用用户对应的权限也要有ROLE_这个前缀才可以。
如果直接向下面这种写法然后直接执行的话,会访问不了。
@PreAuthorize("hasRole('test')")
我们用户即使有这个权限,也会访问不了,我们来看一下这个对应的源码。
在hasRole这个方法内部又调用了一个方法hasAnyRole,
这个方法在传入参数的时候,this.defaultRolePrefix 传进来一个 ‘ROLE_’ 。是role的一个前缀,而这个 “ROLE_” 就是默认的前缀。
在hasAnyAuthorityName方法内部,它获取到了这个权限,还要拿这个权限拼接上一个前缀,拼接之后再和集合中的元素做一个判断。
hasAnyRole有任意的角色就可以访问。它内部也会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有**ROLE_**这个前缀才可以。
@PreAuthorize("hasRole('admin','test')")