spring security 自定义AccessDecisionVoter类

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两种方式进行配置。

猜你喜欢

转载自blog.csdn.net/neweastsun/article/details/80633421