Spring Security 基于URL的权限判断
1. FilterSecurityInterceptor 源码阅读
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
通过过滤器实现对HTTP资源进行安全处理。
该安全拦截器所需的 SecurityMetadataSource 类型为 FilterInvocationSecurityMetadataSource。
doFilter方法中直接调用invoke方法
基本都是调用父类的方法,那下面就重点看下父类 AbstractSecurityInterceptor 中相关方法
为安全对象实现安全拦截的抽象类。
AbstractSecurityInterceptor 将确保安全拦截器的正确启动配置。 它还将实现对安全对象调用的正确处理,即:
- 从 SecurityContextHolder 获取 Authentication 对象。
- 通过在SecurityMetadataSource中查找安全对象请求,确定请求是与安全调用还是公共调用相关(PS:简单地来讲,就是看一下请求的资源是不是受保护的,受保护的就是安全调用,就要权限,不受保护的就不需要权限就可以访问)。
- 对于受保护的调用(有一个用于安全对象调用的 ConfigAttributes 列表):
- 如果 Authentication.isAuthenticated() 返回 false,或者 alwaysReauthenticate 为 true,则根据配置的 AuthenticationManager 对请求进行身份验证。 通过身份验证后,将 SecurityContextHolder 上的 Authentication 对象替换为返回值。
- 根据配置的AccessDecisionManager授权请求。
- 通过配置的RunAsManager执行任何run-as替换。
- 将控制权传递回具体的子类,它实际上将继续执行对象。返回一个 InterceptorStatusToken 以便在子类完成对象的执行后,其 finally 子句可以确保 AbstractSecurityInterceptor 被调用并使用 finallyInvocation(InterceptorStatusToken) 正确处理。
- 具体的子类将通过 afterInvocation(InterceptorStatusToken, Object) 方法重新调用 AbstractSecurityInterceptor。
- 如果 RunAsManager 替换了 Authentication 对象,则将 SecurityContextHolder 返回到调用 AuthenticationManager 后存在的对象。
- 如果定义了AfterInvocationManager,则调用它并允许它替换将要返回给调用者的对象。
- 对于公开的调用(安全对象调用没有 ConfigAttributes):
- 如上所述,具体的子类将返回一个 InterceptorStatusToken,在执行完安全对象后,该 InterceptorStatusToken 随后被重新呈现给 AbstractSecurityInterceptor。 AbstractSecurityInterceptor 在它的 afterInvocation(InterceptorStatusToken, Object) 被调用时不会采取进一步的行动。
- 控制再次返回到具体的子类,以及应该返回给调用者的对象。然后子类会将该结果或异常返回给原始调用者。
下面具体来看
从这里我们可以知道返回null和空集合是一样的。
接下来看授权
这是我们要重点关注的,可以看到,授权靠的是 accessDecisionManager.decide(authenticated, object, attributes)
因此,我们想要实现自己的基于请求Url的授权只需自定义一个 AccessDecisionManager 即可
接下来,我们来具体实现一下
2. 自定义基于url的授权
先把Spring Security授权的大致流程流程摆在这儿:
自定义FilterInvocationSecurityMetadataSource
package com.example.security.core;
import com.example.security.service.SysPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* @Author ChengJianSheng
* @Date 2021/12/2
*/
@Component
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private SysPermissionService sysPermissionService;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
String httpMethod = fi.getRequest().getMethod();
List<ConfigAttribute> attributes = new ArrayList<>();
Map<String, String> urlRoleMap = sysPermissionService.getAllUrlRole();
for (Map.Entry<String, String> entry : urlRoleMap.entrySet()) {
if (antPathMatcher.match(entry.getKey(), url)) {
return SecurityConfig.createList(entry.getValue());
}
}
// 返回null和空列表是一样的,都表示当前访问的资源不需要权限,所有人都可以访问
return attributes;
// return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}