如何动态决定访问Spring Security中的属性值?

发布于 2024-11-27 00:32:33 字数 402 浏览 2 评论 0 原文

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

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

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

In Spring Security we use the intercept-url tag to define the access for URLs as below:

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

This is hard coded in applicationContext-security.xml. I want to read the access values from a database table instead. I have defined my own UserDetailsService and I read the roles for the logged in user from the database. How do I assign these roles to the URL patterns during runtime?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

独行侠 2024-12-04 00:32:33

Spring-security 中的 FilterIncationSecurityMetadataSourceParser 类(在带有源代码的 STS 中尝试 Ctrl/Cmd+Shift+T)解析拦截 url 标签并创建 ExpressionBasedFilterInvocalSecurityMetadataSource 的实例,该实例扩展了 DefaultFilterInitationSecurityMetadataSource,后者实现了扩展 SecurityMetadataSource 的 FilterInitationSecurityMetadataSource。

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

然后你必须实现这些方法:

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

  • booleansupports(Class clazz)

  • Collection 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>

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

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

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

The FilterInvocationSecurityMetadataSourceParser class in Spring-security (try Ctrl/Cmd+Shift+T in STS with the source code) parses the intercept-url tags and creates instances of ExpressionBasedFilterInvocationSecurityMetadataSource, that extends DefaultFilterInvocationSecurityMetadataSource that implements FilterInvocationSecurityMetadataSource that extends SecurityMetadataSource.

What I did is to create a custom class that implements FilterInvocationSecurityMetadataSource, OptionsFromDataBaseFilterInvocationSecurityMetadataSource. I used DefaultFilterInvocationSecurityMetadataSource as base to use urlMatcher, to implement the support() method and something like that.

Then you must to implement these methods:

  • Collection getAttributes(Object object), where you can access to database, searching for the 'object' being secured (normally the URL to access) to obtain the allowed ConfigAttribute's (normally the ROLE's)

  • boolean supports(Class clazz)

  • Collection getAllConfigAttributes()

Be careful with the later, because it's called at startup and maybe is not well configured at this time (I mean, with the datasources or persistence context autowired, depending on what are you using). The solution in a web environment is to configure the contextConfigLocation in the web.xml to load the applicationContext.xml before the applicationContext-security.xml

The final step is to customize the applicationContext-security.xml to load this bean.

For doing that, I used regular beans in this file instead of the security namespace:

    <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>

You have to define all the related beans. For instance:

    <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>

I know that is not a well explained answer, but it's not as difficult as it seems.

Just use the spring source as base and you will obtain what you want.

Debugging with the data in your database, will help you a lot.

我不咬妳我踢妳 2024-12-04 00:32:33

实际上,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 的数据库角色

Actually, spring security 3.2 do not encourage to do this according to http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-dynamic-url-metadata

but, it is possible (but not elegant) using http element in namespace with a custom accessDecisionManager..

The config should be:

<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>

The CustomAccessDecisionManager should be...

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();
}

...
}

Where getConfigAttributesFromSecuredUris retrieve form DB de roles for the specific URL

李不 2024-12-04 00:32:33

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

spring的JIRA中有一个提案,关于这个问题。

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

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

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

你怎么认为?

I have kind of the same problem, basically I'd like to keep separate the list of intercept-url from the other springsecurity configuration section, the first to belong to the application configuration the latter to the product (core, plugin) configuration.

There is a proposal in the JIRA of spring, concerning this problem.

I don't want to give up to use the springsecurity namespace, so I was thinking to some possible solutions in order to deal with this.

In order to have the list of intercept-url dynamically created you have to inject the securitymetadatasource object in the FilterSecurityInterceptor.
Using springsecurity schema the instance of FilterSecurityInterceptor is created by the HttpBuilder class and there is no way to pass the securitymetadatasource as property defined in the schema configuration file, as less as using kind of workaround, which could be:

  • Define a custom filter, to be executed before FilterSecurityInterceptor, in this filter retrieving the instance FilterSecurityInterceptor (assuming a unique http section is defined) by the spring context and inject there the securitymetadatasource instance;
  • The same as above but in a HandlerInterceptor.

What do you think?

前事休说 2024-12-04 00:32:33

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

<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> 

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

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

希望这有帮助。

This the solution I've applied in order to split the list of intercept-url entries from the other spring security configuration.

<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>

The bean securityMetadataSource can be put either in the same configuration file or in another configuration file.

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

Of course you can decide to implement your own securityMetadataSource bean by implementing the interface FilterInvocationSecurityMetadataSource.
Something like this:

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

Hope this helps.

南七夏 2024-12-04 00:32:33

在 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 */
    }
}

This is how it can be done in 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 */
    }
}
终陌 2024-12-04 00:32:33

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

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

一个 bean :

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

customAuthenticationProvider 是CustomAuthenticationProvider 类创建方法中的

public synchronized String getReturnStringMethod()
{
    //get data from database (call your method)

    if(condition){
        return "IS_AUTHENTICATED_ANONYMOUSLY";
    }
    return "ROLE_ADMIN,ROLE_USER";
}

A simple solution that works for me.

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

customAuthenticationProvider is a bean

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

in CustomAuthenticationProvider class create method:

public synchronized String getReturnStringMethod()
{
    //get data from database (call your method)

    if(condition){
        return "IS_AUTHENTICATED_ANONYMOUSLY";
    }
    return "ROLE_ADMIN,ROLE_USER";
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文