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 getAttributes(Object object) {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
String httpMethod = fi.getRequest().getMethod();
List attributes = new ArrayList();
// Lookup your database (or other source) using this information and populate the
// list of attributes
return attributes;
}
public Collection getAllConfigAttributes() {
return null;
}
public boolean supports(Class> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
具体思路就是通过自定义过滤器的MetadataSource
来实现规则的灵活配置, 该部分实例默认使用的是DefaultFilterInvocationSecurityMetadataSource
, 可以根据这里的源码来编写自己的MetadataSource.
内部使用下面这个结构来维护匹配规则和对应的权限集
Map> requestMap
RequestMatcher和ConfigAttribute都是抽象类, 需要找个能用的类, 通过查阅源码可以找到RequestMathcer的具体构造实现
RequestMatchers.antMatchers(...)
不过该方法不能直接拿来用, 写了私有
public static List antMatchers(HttpMethod httpMethod,
String... antPatterns) {
String method = httpMethod == null ? null : httpMethod.toString();
List 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也是. 有这么几个其实写之前就该考虑好的问题.
其实spring官方是推荐方法级权限就直接硬编码的. 因为考虑到放在数据库后, 安全上的风险实在太大了. 仅仅通过修改数据库, 即使非admin角色的账户也是能获取所有的操作权限的. 另一点是操作权限定义上的变更(哪些角色该有哪些操作权限?)本身就应该是需要审计的, 并且非常低频的. 硬编码在排除风险之余, 对于实际使用的影响其实也是微乎其微的(无非每次确定要改了, 发一次版)
至于我为何要写自定义的FilterSecurityInterceptor
, 主要是系统的security集成在路由层, 那边不定义方法, 法2在configure里硬编码好像又太繁琐, 所以想在Metadatasource层用文件或者什么静态常量的方法硬编码.
不过最后写完发现好像也不便利?并不快乐??? 为了这个目标多写了好多实现类, 并不能轻松愉快地直接注入Metadatasource.
如果有其他朋友找到了更加便捷的注入方式, 还请指明具体操作~不胜感激.