如何在<intercept-url>Spring Security中动态决定访问属性值?



在Spring Security中,我们使用intercept url标记来定义url的访问权限,如下所示:

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

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

Spring安全中的FilterInvocationSecurityMetadataSourceParser类(在STS中使用源代码尝试Ctrl/Cmd+Shift+T)解析截取url标记并创建ExpressionBasedFilterInvocationSecurity MetadataSource的实例,扩展DefaultFilterInvocationSecurityMetadataSource,实现扩展SecurityMetadataSource的FilterInvocationSecurity MetadataSource。

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

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

  • 集合getAttributes(对象对象),您可以在其中访问数据库,搜索被保护的"对象"(通常是要访问的URL)以获得允许的ConfigAttribute(通常是ROLE)

  • 布尔型支持(Class 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>

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

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

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

实际上,spring security 3.2不鼓励根据http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-动态url元数据

但是,在命名空间中使用http元素和自定义accessDecisionManager是可能的(但并不优雅)。。

配置应该是:

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

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

春季的JIRA中有一个关于这个问题的建议。

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

为了动态创建拦截url列表,您必须在FilterSecurityInterceptor中注入securitymetadatasource对象。使用springsecurity架构,FilterSecurityInterceptor的实例由HttpBuilder类创建,并且没有办法将securitymetadatasource作为架构配置文件中定义的属性进行传递,也没有办法使用某种变通方法,可以是:

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

你怎么看?

这是我应用的解决方案,目的是从其他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来实现自己的securityMetadataSourcebean。类似这样的东西:

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

希望这能有所帮助。

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

一个对我有用的简单解决方案。

<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";
}

最新更新