Skip to main content
 首页 » 编程设计

spring security 自定义AccessDecisionVoter类

2022年07月19日127lautakyan007

spring security 自定义AccessDecisionVoter类

针对spring web应用或rest api,spring security提供很多方法实现安全控制,但有时会遇到一些具体场景默认提供功能难以满足。本文我们自定义AccessDecisionVoter类展示如何抽象web应用程序的授权逻辑,并将其与应用程序的业务逻辑分离开来。

应用场景

为了演示AccessDecisionVoter类的如何工作,假设有两种类型用户,USER和ADMIN,USER仅可以在偶数分钟访问系统,而ADMIN总是可以访问。实际项目中可能用于更细的权限判断或url拦截等功能。

AccessDecisionVoter实现

首先,我们将描述Spring提供的一些实现,它们将与我们的自定义投票者一起参与对授权做出最终决定。然后,我们将了解如何实现自定义投票者。

缺省的AccessDecisionVoter实现

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进行配置:

<http use-expressions="true"> 
    <intercept-url pattern="/" 
      access="hasAuthority('ROLE_USER')"/> 
    ... 
</http> 

自定义AccessDecisionVoter实现

自定义AccessDecisionVoter实现,需要实现AccessDecisionVoter 接口:

public class MinuteBasedVoter implements AccessDecisionVoter { 
   ... 
} 

三个必须提供的方法中首先是vote方法,其中实现授权业务逻辑,是我们自定义投票场景中最重要的方法。
vote方法能返回三种可能的值:

  • ACCESS_GRANTED – 同意
  • ACCESS_DENIED – 拒绝
  • ACCESS_ABSTAIN – 弃权

下面是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

最终授权决策由AccessDecisionManager处理。AbstractAccessDecisionManager 包括一组AccessDecisionVoter,它们各自相互独立进行投票。大多数场景中需要这三个处理投票实现:

AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
ConsensusBased – 同意投票多于拒绝投票(忽略弃权回答)则允许访问
UnanimousBased – 每个投票者选择弃权或同意则允许访问
当然你能根据实际业务逻辑实现你自己的AccessDecisionManager.

配置

本节我们通过java config方式和xml方式配置自定义的AccessDecisionVoter 及AccessDecisionManager。

java config

@Configuration 
@EnableWebSecurity 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
... 
} 

定义AccessDecisionManager bean 使用UnanimousBased管理器管理所以投票决策:

@Bean 
public AccessDecisionManager accessDecisionManager() { 
    List<AccessDecisionVoter<? extends Object>> 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 配置

如果使用xml配置,你需要修改spring-security.xml 文件,首先需要修改标签:

<http access-decision-manager-ref="accessDecisionManager"> 
  <intercept-url 
    pattern="/**" 
    access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')"/> 
  ... 
</http> 

增加自定义投票的bean:

<beans:bean 
  id="minuteBasedVoter" 
  class="org.baeldung.voter.MinuteBasedVoter"/> 

增加AccessDecisionManagerbean:

<beans:bean 
  id="accessDecisionManager" 
  class="org.springframework.security.access.vote.UnanimousBased"> 
    <beans:constructor-arg> 
        <beans:list> 
            <beans:bean class= 
              "org.springframework.security.web.access.expression.WebExpressionVoter"/> 
            <beans:bean class= 
              "org.springframework.security.access.vote.AuthenticatedVoter"/> 
            <beans:bean class= 
              "org.springframework.security.access.vote.RoleVoter"/> 
            <beans:bean class= 
              "org.baeldung.voter.MinuteBasedVoter"/> 
        </beans:list> 
    </beans:constructor-arg> 
</beans:bean> 

示例标记支持测试场景:

<authentication-manager> 
    <authentication-provider> 
        <user-service> 
            <user name="user" password="pass" authorities="ROLE_USER"/> 
            <user name="admin" password="pass" authorities="ROLE_ADMIN"/> 
        </user-service> 
    </authentication-provider> 
</authentication-manager> 

如果使用混合方式配置,可以导入xml至配置类:

@Configuration 
@ImportResource({"classpath:spring-security.xml"}) 
public class XmlSecurityConfig { 
    public XmlSecurityConfig() { 
        super(); 
    } 
} 

总结

文本我们演示使用AccessDecisionVoter实现自定义web应用授权。学习了spring security自带的投票实现以及我们自定义的投票实现。然后我们讨论了负责处理投票决策的AccessDecisionManager类,其决定所有投票完成后如何最终决策是否授权。最后我们介绍xml和java两种方式进行配置。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/80633421