Как динамически выбрать значение атрибута доступа <intercept-url> в Spring Безопасность?
В Spring безопасности мы используем тег intercept-url для определения доступа для URL-адресов, как показано ниже:
<intercept-url pattern="/**" access="ROLE_ADMIN" />
<intercept-url pattern="/student" access="ROLE_STUDENT" />
Это жестко закодировано в applicationContext-security.xml
. Вместо этого я хочу прочитать значения доступа из таблицы базы данных. Я определил свой собственный UserDetailsService
, и я прочитал роли для зарегистрированного пользователя из базы данных. Как назначить эти роли шаблонам URL во время выполнения?
Ответы
Ответ 1
Класс FilterInvocationSecurityMetadataSourceParser в Spring -security (попробуйте Ctrl/Cmd + Shift + T в STS с исходным кодом) анализирует теги intercept-url и создает экземпляры ExpressionBasedFilterInvocationSecurityMetadataSource, который расширяет DefaultFilterInvocationSecurityMetadataSource, который реализует FilterInvocationSecurityMetadataSource, который расширяет SecurityMetadataSource.
Я создал класс, который реализует FilterInvocationSecurityMetadataSource, OptionsFromDataBaseFilterInvocationSecurityMetadataSource. Я использовал DefaultFilterInvocationSecurityMetadataSource как базу для использования urlMatcher, чтобы реализовать метод support() и что-то в этом роде.
Затем вы должны реализовать эти методы:
-
Коллекция getAttributes (объект объекта), где вы можете получить доступ к базе данных, ища защищенный объект (обычно URL-адрес для доступа), чтобы получить разрешенный ConfigAttribute (обычно ROLE)
-
boolean поддерживает (Class clazz)
-
Коллекция getAllConfigAttributes()
Будьте осторожны с более поздним, потому что он вызвал при запуске и, возможно, не настроен в данный момент (я имею в виду, что с источниками данных или контекстом постоянства автоматически, в зависимости от того, что вы используете). Решение в веб-среде состоит в том, чтобы настроить contextConfigLocation в web.xml для загрузки applicationContext.xml перед applicationContext-security.xml
Последний шаг - настроить applicationContext-security.xml для загрузки этого bean.
Для этого я использовал обычный beans в этом файле вместо пространства имен безопасности:
<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>
Вы должны определить все связанные beans. Например:
<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 как базу, и вы получите то, что хотите.
Отладка с данными в вашей базе данных поможет вам многое.
Ответ 2
На самом деле, spring security 3.2 не рекомендуется делать это в соответствии с http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-dynamic-url-metadata
но возможно (но не изящно) с помощью 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 извлекает форму DB для ролей для определенного URL
Ответ 3
У меня есть одна и та же проблема, в основном я бы хотел разделить список перехвата-url из другого раздела конфигурации springsecurity, первый из которых относится к конфигурации приложения последнему к продукту (ядро, плагин) конфигурации.
В JIRA spring есть предложение в отношении этой проблемы.
Я не хочу отказаться от использования пространства имен springsecurity, поэтому я подумал о некоторых возможных решениях, чтобы справиться с этим.
Чтобы список динамического создания перехвата-url, вам нужно ввести объект securitymetadatasource в FilterSecurityInterceptor.
Используя springsecurity schema, экземпляр FilterSecurityInterceptor создается классом HttpBuilder, и нет способа передать свойство securitymetadatasource как свойство, определенное в файле конфигурации схемы, меньше, чем использование метода обхода, которое может быть:
- Определите настраиваемый фильтр, который должен быть выполнен до FilterSecurityInterceptor, в этом фильтре, который получает экземпляр FilterSecurityInterceptor (при условии, что определен уникальный раздел http определен) в контексте spring и вставляет туда экземпляр securitymetadatasource;
- То же, что и выше, но в HandlerInterceptor.
Как вы думаете?
Ответ 4
Это решение, которое я применил, чтобы разбить список записей перехвата-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>
Конечно, вы можете решить реализовать свой собственный securityMetadataSource bean, реализовав интерфейс FilterInvocationSecurityMetadataSource.
Что-то вроде этого:
<bean id="securityMetadataSource" class="mypackage.MyImplementationOfFilterInvocationSecurityMetadataSource" />
Надеюсь, что это поможет.
Ответ 5
Простое решение, которое работает для меня.
<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";
}
Ответ 6
Вот как это можно сделать в 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 */
}
}