针对spring web应用或rest api,spring security提供很多方法实现安全控制,但有时会遇到一些具体场景默认提供功能难以满足。本文我们自定义AccessDecisionVoter类展示如何抽象web应用程序的授权逻辑,并将其与应用程序的业务逻辑分离开来。
为了演示AccessDecisionVoter类的如何工作,假设有两种类型用户,USER和ADMIN,USER仅可以在偶数分钟访问系统,而ADMIN总是可以访问。实际项目中可能用于更细的权限判断或url拦截等功能。
首先,我们将描述Spring提供的一些实现,它们将与我们的自定义投票者一起参与对授权做出最终决定。然后,我们将了解如何实现自定义投票者。
spring security提供几个AccessDecisionVoter,我们将使用其中一些作为解决方案的部分内容,并了解什么时候如何使用这些缺省实现。
AuthenticatedVoter 根据认证对象的级别进行投票,具体查询完整认证用户、记住我认证或匿名认证.
RoleVoter 基于任何一个以“ROLE_”开头的配置属性进行投票. 如果符合条件,则搜索认证对象的GrantedAuthority列表.
WebExpressionVoter 允许我们使用 SpEL (Spring Expression Language) 去授权使用@PreAuthorize注解的请求.
举例,使用java config:
@Override
protected void configure(final HttpSecurity http) throws Exception {
...
.antMatchers("/").hasAnyAuthority("ROLE_USER")
...
}
或者使用xml配置——我们使用SPEL进行配置:
...
自定义AccessDecisionVoter实现,需要实现AccessDecisionVoter 接口:
public class MinuteBasedVoter implements AccessDecisionVoter {
...
}
三个必须提供的方法中首先是vote方法,其中实现授权业务逻辑,是我们自定义投票场景中最重要的方法。
vote方法能返回三种可能的值:
下面是vote方法的逻辑实现:
@Override
public int vote(
Authentication authentication, Object object, Collection collection) {
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.filter(r -> "ROLE_USER".equals(r)
&& LocalDateTime.now().getMinute() % 2 != 0)
.findAny()
.map(s -> ACCESS_DENIED)
.orElseGet(() -> ACCESS_ABSTAIN);
}
在我们vote方法中,首先检查如果请求来自USER,如果时间是偶数分钟则返回ACCESS_GRANTED,反之返回ACCESS_DENIED。如果请求不来自USER返回ACCESS_ABSTAIN。
第二个方法返回投票是否支持特定配置属性,在我们示例中,无需特殊属性,所以总是返回true。
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
第三个方法返回投票是否可以为受保护对象类型投票。由于我们的投票不关心受保护对象类型,所以返回true:
@Override
public boolean supports(Class clazz) {
return true;
}
最终授权决策由AccessDecisionManager处理。AbstractAccessDecisionManager 包括一组AccessDecisionVoter,它们各自相互独立进行投票。大多数场景中需要这三个处理投票实现:
AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
ConsensusBased – 同意投票多于拒绝投票(忽略弃权回答)则允许访问
UnanimousBased – 每个投票者选择弃权或同意则允许访问
当然你能根据实际业务逻辑实现你自己的AccessDecisionManager.
本节我们通过java config方式和xml方式配置自定义的AccessDecisionVoter 及AccessDecisionManager。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}
定义AccessDecisionManager bean 使用UnanimousBased管理器管理所以投票决策:
@Bean
public AccessDecisionManager accessDecisionManager() {
List> decisionVoters
= Arrays.asList(
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter(),
new MinuteBasedVoter());
return new UnanimousBased(decisionVoters);
}
最后配置spring security,使用之前定义的bean作为缺省的accessDecisionManager:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.anyRequest()
.authenticated()
.accessDecisionManager(accessDecisionManager());
}
如果使用xml配置,你需要修改spring-security.xml 文件,首先需要修改标签:
...
增加自定义投票的bean:
增加AccessDecisionManagerbean:
示例标记支持测试场景:
如果使用混合方式配置,可以导入xml至配置类:
@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class XmlSecurityConfig {
public XmlSecurityConfig() {
super();
}
}
文本我们演示使用AccessDecisionVoter实现自定义web应用授权。学习了spring security自带的投票实现以及我们自定义的投票实现。然后我们讨论了负责处理投票决策的AccessDecisionManager类,其决定所有投票完成后如何最终决策是否授权。最后我们介绍xml和java两种方式进行配置。