【问题标题】:How to dynamically decide <intercept-url> access attribute value in Spring Security?如何在 Spring Security 中动态决定 <intercept-url> 访问属性值?
【发布时间】:2011-10-17 02:36:59
【问题描述】:

在 Spring Security 中,我们使用 intercept-url 标签来定义对 URL 的访问,如下所示:

<intercept-url pattern="/**" access="ROLE_ADMIN" />
<intercept-url pattern="/student" access="ROLE_STUDENT" />

这是在applicationContext-security.xml 中硬编码的。我想改为从数据库表中读取访问值。我已经定义了自己的UserDetailsService,并从数据库中读取了登录用户的角色。如何在运行时将这些角色分配给 URL 模式?

【问题讨论】:

标签: spring-security authorization security-roles


【解决方案1】:

Spring-security 中的 FilterInvocationSecurityMetadataSourceParser 类(使用源代码在 STS 中尝试 Ctrl/Cmd+Shift+T)解析拦截 url 标签并创建 ExpressionBasedFilterInvocationSecurityMetadataSource 的实例,该实例扩展 DefaultFilterInvocationSecurityMetadataSource 实现 FilterInvocationSecurityMetadataSource 扩展 SecurityMetadataSource。

我所做的是创建一个自定义类来实现 FilterInvocationSecurityMetadataSource,OptionsFromDataBaseFilterInvocationSecurityMetadataSource。我使用 DefaultFilterInvocationSecurityMetadataSource 作为基础来使用 urlMatcher,来实现 support() 方法和类似的东西。

那么你必须实现这些方法:

  • Collection getAttributes(Object object),您可以在其中访问数据库,搜索受保护的“对象”(通常是要访问的 URL)以获得允许的 ConfigAttribute(通常是 ROLE)

  • 布尔支持(类clazz)

  • 集合 getAllConfigAttributes()

以后要小心,因为它是在启动时调用的,并且此时可能没有很好地配置(我的意思是,自动装配数据源或持久性上下文,具体取决于您使用的内容)。 web环境下的解决方案是在web.xml中配置contextConfigLocation,在applicationContext-security.xml之前加载applicationContext.xml

最后一步是自定义applicationContext-security.xml来加载这个bean。

为此,我在此文件中使用了常规 bean,而不是安全命名空间:

    <beans:bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
    <filter-chain-map path-type="ant">
        <filter-chain pattern="/images/*" filters="none" />
        <filter-chain pattern="/resources/**" filters="none" />
        <filter-chain pattern="/**" filters="
        securityContextPersistenceFilter,
        logoutFilter,
        basicAuthenticationFilter,
        exceptionTranslationFilter,
        filterSecurityInterceptor" 
    />
    </filter-chain-map>
</beans:bean>

您必须定义所有相关的 bean。例如:

    <beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
    <beans:property name="accessDecisionManager" ref="affirmativeBased"></beans:property>
    <beans:property name="securityMetadataSource" ref="optionsFromDataBaseFilterInvocationSecurityMetadataSource"></beans:property>
    <beans:property name="validateConfigAttributes" value="true"/></beans:bean>

我知道这不是一个很好解释的答案,但它并不像看起来那么难。

只需使用弹簧源作为基础,你就会得到你想要的。

使用数据库中的数据进行调试,对您有很大帮助。

【讨论】:

  • 嗨,我正在尝试实现相同的目标,但使用 struts2。经历了 Spring Security 3.1,但无法获得有关与 struts2 集成的足够想法。您介意提供一些与 struts2 集成的基本想法吗?
  • 这个例子是在老版本的spring下。我如何在 spring-security 3.2 下做到这一点?
【解决方案2】:

其实spring security 3.2不鼓励按照http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-dynamic-url-metadata这样做

但是,可以(但不优雅)在命名空间中使用带有自定义 accessDecisionManager 的 http 元素..

配置应该是:

<http pattern="/login.action" security="none"/>
<http pattern="/media/**" security="none"/>

<http access-decision-manager-ref="accessDecisionManager" >
    <intercept-url pattern="/**" access="ROLE_USER"/>
    <form-login login-page="/login.action"
                authentication-failure-url="/login?error=1"
                default-target-url="/console.action"/>
    <logout invalidate-session="true" delete-cookies="JSESIONID"/>
    <session-management session-fixation-protection="migrateSession">
        <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login.action"/>
    </session-management>

    <!-- NO ESTA FUNCIONANDO, los tokens no se ponen en el request!
    <csrf />
     -->

</http>
<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="test" password="test" authorities="ROLE_USER" />
        </user-service>
    </authentication-provider>
</authentication-manager>

<beans:bean id="accessDecisionManager" class="openjsoft.core.services.security.auth.CustomAccessDecisionManager">
    <beans:property name="allowIfAllAbstainDecisions" value="false"/>
    <beans:property name="decisionVoters">
    <beans:list>
        <beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
    </beans:list>
    </beans:property>
</beans:bean>

CustomAccessDecisionManager 应该是...

public class CustomAccessDecisionManager extends AbstractAccessDecisionManager  {
...

public void decide(Authentication authentication, Object filter,
        Collection<ConfigAttribute> configAttributes)
        throws AccessDeniedException, InsufficientAuthenticationException {

  if ((filter == null) || !this.supports(filter.getClass())) {
        throw new IllegalArgumentException("Object must be a FilterInvocation");
    }

    String url = ((FilterInvocation) filter).getRequestUrl();
    String contexto = ((FilterInvocation) filter).getRequest().getContextPath();

    Collection<ConfigAttribute> roles = service.getConfigAttributesFromSecuredUris(contexto, url);



    int deny = 0;

    for (AccessDecisionVoter voter : getDecisionVoters()) {
        int result = voter.vote(authentication, filter, roles);

        if (logger.isDebugEnabled()) {
            logger.debug("Voter: " + voter + ", returned: " + result);
        }

        switch (result) {
        case AccessDecisionVoter.ACCESS_GRANTED:
            return;

        case AccessDecisionVoter.ACCESS_DENIED:

            deny++;

            break;

        default:
            break;
        }
    }

    if (deny > 0) {
        throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
                "Access is denied"));
    }

    // To get this far, every AccessDecisionVoter abstained
    checkAllowIfAllAbstainDecisions();
}

...
}

getConfigAttributesFromSecuredUris 在哪里检索特定 URL 的 DB 角色

【讨论】:

    【解决方案3】:

    我有同样的问题,基本上我想将intercept-url列表与其他springsecurity配置部分分开,第一个属于应用程序配置,后者属于产品(核心,插件)配置。

    spring的JIRA中有一个proposal,就是关于这个问题的。

    我不想放弃使用 springsecurity 命名空间,所以我正在考虑一些可能的解决方案来解决这个问题。

    为了动态创建拦截 URL 列表,您必须在 FilterSecurityInterceptor 中注入 securitymetadatasource 对象。 使用 springsecurity 模式,FilterSecurityInterceptor 的实例是由 HttpBuilder 类创建的,并且无法将 securitymetadatasource 作为模式配置文件中定义的属性传递,不如使用一种解决方法,这可能是:

    • 定义一个自定义过滤器,在 FilterSecurityInterceptor 之前执行,在此过滤器中通过 spring 上下文检索实例 FilterSecurityInterceptor(假设定义了唯一的 http 部分)并在那里注入 securitymetadatasource 实例;
    • 与上述相同,但在 HandlerInterceptor 中。

    你怎么看?

    【讨论】:

      【解决方案4】:

      这是我应用的解决方案,以便从其他 spring 安全配置中拆分拦截 URL 条目列表。

      <security:custom-filter ref="parancoeFilterSecurityInterceptor"
              before="FILTER_SECURITY_INTERCEPTOR" />
      ........
      
      <bean id="parancoeFilterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor" >  
          <property name="authenticationManager" ref="authenticationManager"/>
          <property name="accessDecisionManager" ref="accessDecisionManager"/>
          <property name="securityMetadataSource" ref="securityMetadataSource"/>
      </bean>
      

      bean securityMetadataSource 可以放在同一个配置文件中,也可以放在另一个配置文件中。

      <security:filter-security-metadata-source
          id="securityMetadataSource" use-expressions="true">
          <security:intercept-url pattern="/admin/**"
              access="hasRole('ROLE_ADMIN')" />
      </security:filter-security-metadata-source> 
      

      当然,您可以决定通过实现接口FilterInvocationSecurityMetadataSource 来实现自己的securityMetadataSource bean。 像这样的:

      <bean id="securityMetadataSource" class="mypackage.MyImplementationOfFilterInvocationSecurityMetadataSource" />
      

      希望这会有所帮助。

      【讨论】:

      • 我最终得到了相同的解决方案。可惜没有简单的方法来拆分定义。
      【解决方案5】:

      这是在 Spring Security 3.2 中可以做到的:

      @Configuration
      @EnableWebMvcSecurity
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Bean
          public SecurityConfigDao securityConfigDao() {
              SecurityConfigDaoImpl impl = new SecurityConfigDaoImpl() ;
              return impl ;
          }
      
      
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              /* get a map of patterns and authorities */
              Map<String,String> viewPermissions = securityConfigDao().viewPermissions() ;
      
               ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry interceptUrlRegistry = http
              .authorizeRequests().antMatchers("/publicAccess/**")
              .permitAll(); 
      
              for (Map.Entry<String, String> entry: viewPermissions.entrySet()) {
                  interceptUrlRegistry.antMatchers(entry.getKey()).hasAuthority(entry.getValue());
              }
      
              interceptUrlRegistry.anyRequest().authenticated()
              .and()
              ...
              /* rest of the configuration */
          }
      }
      

      【讨论】:

        【解决方案6】:

        一个适合我的简单解决方案。

        <intercept-url pattern="/**/**" access="#{@customAuthenticationProvider.returnStringMethod}" />
        <intercept-url pattern="/**" access="#{@customAuthenticationProvider.returnStringMethod}" />
        

        customAuthenticationProvider 是一个 bean

        <beans:bean id="customAuthenticationProvider"
            class="package.security.CustomAuthenticationProvider" />
        

        在 CustomAuthenticationProvider 类中创建方法:

        public synchronized String getReturnStringMethod()
        {
            //get data from database (call your method)
        
            if(condition){
                return "IS_AUTHENTICATED_ANONYMOUSLY";
            }
            return "ROLE_ADMIN,ROLE_USER";
        }
        

        【讨论】:

          猜你喜欢
          • 2012-03-02
          • 2014-11-28
          • 2011-01-07
          • 2011-10-16
          • 2012-08-03
          • 1970-01-01
          • 2013-07-03
          • 2019-02-12
          • 2017-01-18
          相关资源
          最近更新 更多