序
对于使用spring security来说,存在一种需求,就是动态去配置url的权限,即在运行时去配置url对应的访问角色。这里简单介绍一下。
Standard Filter Aliases and Ordering
首先需要了解spring security内置的各种filter:
Alias | Filter Class | Namespace Element or Attribute |
---|---|---|
CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |
SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |
CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | session-management/concurrency-control |
HEADERS_FILTER | HeaderWriterFilter | http/headers |
CSRF_FILTER | CsrfFilter | http/csrf |
LOGOUT_FILTER | LogoutFilter | http/logout |
X509_FILTER | X509AuthenticationFilter | http/x509 |
PRE_AUTH_FILTER | AbstractPreAuthenticatedProcessingFilter Subclasses | N/A |
CAS_FILTER | CasAuthenticationFilter | N/A |
FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |
BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |
SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareRequestFilter | http/@servlet-api-provision |
JAAS_API_SUPPORT_FILTER | JaasApiIntegrationFilter | http/@jaas-api-provision |
REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |
ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |
SESSION_MANAGEMENT_FILTER | SessionManagementFilter | session-management |
EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |
FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |
SWITCH_USER_FILTER | SwitchUserFilter | N/A |
这里我们要操作的是FilterSecurityInterceptor这个interceptor,使用withObjectPostProcessor来设置
FilterSecurityInterceptor
这个filter有几个要素,如下:
- SecurityMetadataSource
- AccessDecisionManager
- AuthenticationManager
可以根据情况自己去重新设置,这里我们重写一下SecurityMetadataSource用来动态获取url权限配置,还有AccessDecisionManager来进行权限判断。
MyAccessDecisionManager
public class MyAccessDecisionManager implements org.springframework.security.access.AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
//这段代码其实不需要,因为spring-security-core-4.1.4.RELEASE-sources.jar!/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java第215行判断提前返回了,不会进入decide方法
if (CollectionUtils.isEmpty(configAttributes)) {
throw new AccessDeniedException("not allow");
}
Iterator<ConfigAttribute> ite = configAttributes.iterator();
while (ite.hasNext()) {
ConfigAttribute ca = ite.next();
String needRole = ((org.springframework.security.access.SecurityConfig) ca).getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()) {
if(ga.getAuthority().equals(needRole)){
//匹配到有对应角色,则允许通过
return;
}
}
}
//该url有配置权限,但是当然登录用户没有匹配到对应权限,则禁止访问
throw new AccessDeniedException("not allow");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
这里遍历判断该url所需的角色看用户是否具备,有具备则返回,都不具备则抛出AccessDeniedException异常
MyFilterInvocationSecurityMetadataSource
public class MyFilterInvocationSecurityMetadataSource implements org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource {
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
private final Map<String,String> urlRoleMap = new HashMap<String,String>(){{
put("/open/**","ROLE_ANONYMOUS");
put("/health","ROLE_ANONYMOUS");
put("/restart","ROLE_ADMIN");
put("/demo","ROLE_USER");
}};
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
// String httpMethod = fi.getRequest().getMethod();
for(Map.Entry<String,String> entry:urlRoleMap.entrySet()){
if(antPathMatcher.match(entry.getKey(),url)){
return SecurityConfig.createList(entry.getValue());
}
}
//没有匹配到,默认是要登录才能访问
return SecurityConfig.createList("ROLE_USER");
// return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
这里以内存的map来展示一下,实际应用可以从分布式配置中心或者数据库中读取,另外循环遍历这个可能消耗性能,必要时得优化一下。
SecurityConfig
最后需要综合配置一下,如下
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi) {
fsi.setSecurityMetadataSource(mySecurityMetadataSource());
fsi.setAccessDecisionManager(myAccessDecisionManager());
return fsi;
}
});
}
@Bean
public FilterInvocationSecurityMetadataSource mySecurityMetadataSource() {
MyFilterInvocationSecurityMetadataSource securityMetadataSource = new MyFilterInvocationSecurityMetadataSource();
return securityMetadataSource;
}
@Bean
public AccessDecisionManager myAccessDecisionManager() {
return new MyAccessDecisionManager();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。