springboot之security FilterSecurityInterceptor的使用要点记录
作者:一名工程师
spring security FilterSecurityInterceptor使用要点
FilterSecurityInterceptor是一个方法级的权限过滤器, 基本位于过滤链的最底部
该过滤器用于控制method级别的权限控制. 官方提供了2种默认的方法权限控制写法
一种是在方法上加注释实现,另一种是在configure配置中通过
@Secured("ROLE_ADMIN") //法1, 方法定义处加注释, 需先在具体的配置里开启此类配置 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) 法2, 在复写的configure里直接定义 .antMatchers("your match rule").authenticated() .antMatchers("your match rule").hasRole("ADMIN") //使用时权限会自动加前缀ROLE_ADMIN
具体细节的代码就不贴了,官方文档一模一样的都有.
上面两种方法最终都会生成一个FilterSecurityInterceptor实例,放在上面过滤链底部. 用于方法级的鉴权.
官方还提到了第三种方法,关于如何把过滤的规则放到更为灵活的位置,数据库/本地文件/等等.
贴一段官方代码
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { //此方法用于鉴权过程中获取当前的请求URL需要哪种权限 public List<ConfigAttribute> getAttributes(Object object) { FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl(); String httpMethod = fi.getRequest().getMethod(); List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(); // Lookup your database (or other source) using this information and populate the // list of attributes return attributes; } public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
具体思路就是通过自定义过滤器的MetadataSource
来实现规则的灵活配置,该部分实例默认使用的是DefaultFilterInvocationSecurityMetadataSource
, 可以根据这里的源码来编写自己的MetadataSource.
内部使用下面这个结构来维护匹配规则和对应的权限集
Map<RequestMatcher, Collection<ConfigAttribute>> requestMap
RequestMatcher和ConfigAttribute都是抽象类,需要找个能用的类,通过查阅源码可以找到RequestMathcer的具体构造实现
RequestMatchers.antMatchers(...)
不过该方法不能直接拿来用,写了私有
public static List<RequestMatcher> antMatchers(HttpMethod httpMethod, String... antPatterns) { String method = httpMethod == null ? null : httpMethod.toString(); List<RequestMatcher> matchers = new ArrayList<>(); for (String pattern : antPatterns) { matchers.add(new AntPathRequestMatcher(pattern, method)); } return matchers; }
改用new AntPathRequestMatcher(pattern, method)
这个就行
ConfigAttribute有一个叫SecurityConfig
的实例, 构造时传入String就可以构造对应的权限实例
不过官方好像没写具体怎么注入这个自定义的MetadataSource???
找了半天好像都没找到能直接注入到默认的Filter的途径, 没办法只能写一个新的自定义FilterSecurityInterceptor
来注入, 通过configure里的addFilter()
方法放入过滤链
setSecurityMetadataSource()方法写入自定义的rules源, 还需要注入AccessDecision和Authentication,
前者用于验证访问权限, 继承AccessDecisionManager
实现decide方法来编写自定义的验证逻辑
decide
方法包含 Authentication
, 一个Object类型的FilterInvocation
实例, 一组ConfigAttribute
(要求的权限列表, 从MetaSource的getAttributes()
方法里取的, 完整的获取和校验上层逻辑都封装在AbstractSecurityInterceptor
的beforeInvocation()
方法里)
后者用于验证登录授权, 后者使用默认的super.authenticationManager()
即可
完成自定义方法级过滤后碰到几个问题,一个是加入了这个Filter后原先方法1和方法2设置的就都失效了.
这里直接说看源码打断点后的结论, 主要是因为自定义的filter加入后, 和原先的默认FilterSecurityInterceptor
会有互相排斥的问题, 具体表现为只要这两个中的其中一个先执行invoke()方法, 就会在request里追加一个名为__spring_security_filterSecurityInterceptor_filterApplied
的attribute表示FilterSecurityInterceptor
这个类型的过滤器已经执行过了. 当另一个同类的FilterSecurityInterceptor
进来时就直接跳过具体的invoke方法直接执行下一个过滤器了.
过滤器的位置排序上, addFilter()加的自定义FilterSecurityInterceptor
排到了默认的FilterSecurityInterceptor
之前,如果要放在默认的后面, 用addFilterAfter()方法, 指定需要放在哪个过滤器后面.
所以对应的解决办法也很简单,覆写自定义过滤器中的invoke方法,把加attribute
那段去掉.
我就不处理这个问题了, 其实这个地方算不算一个问题还得单独考虑的, 包括上面自定义Metadata也是.
有这么几个其实写之前就该考虑好的问题.
- 系统里的方法级权限真的需要通过数据库灵活配置吗?
- 系统真的需要让自定义的filter和默认filter的权限规则同时生效吗?
其实spring官方是推荐方法级权限就直接硬编码的. 因为考虑到放在数据库后, 安全上的风险实在太大了.
仅仅通过修改数据库,即使非admin角色的账户也是能获取所有的操作权限的.
另一点是操作权限定义上的变更(哪些角色该有哪些操作权限?)本身就应该是需要审计的,并且非常低频的.
硬编码在排除风险之余,对于实际使用的影响其实也是微乎其微的(无非每次确定要改了,发一次版)
至于我为何要写自定义的FilterSecurityInterceptor
, 主要是系统的security集成在路由层,那边不定义方法,法2在configure里硬编码好像又太繁琐,所以想在Metadatasource层用文件或者什么静态常量的方法硬编码.
不过最后写完发现好像也不便利?并不快乐??? 为了这个目标多写了好多实现类,并不能轻松愉快地直接注入Metadatasource.
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。