Comment décider dynamiquement de la valeur d'attribut d'accès dans Spring Security?
Dans Spring Security, nous utilisons la balise intercept-url pour définir l'accès aux URL comme suit:
<intercept-url pattern="/**" access="ROLE_ADMIN" />
<intercept-url pattern="/student" access="ROLE_STUDENT" />
C'est codé en dur dans applicationContext-security.xml
. Je veux lire les valeurs d'accès à partir d'une table de base de données à la place. J'ai défini ma propre UserDetailsService
et j'ai lu les rôles de l'utilisateur connecté à partir de la base de données. Comment puis-je affecter ces rôles aux modèles D'URL pendant l'exécution?
6 réponses
La classe FilterInvocationSecurityMetadatasourceparser dans Spring-security (essayez Ctrl / Cmd + Shift + T dans STS avec le code source) analyse les balises intercept-url et crée des instances de ExpressionBasedFilterInvocationSecuritymetadatasource, qui étend DefaultFilterInvocationSecurityMetadatasource qui implémente FilterInvocationSecurityMetadatasource qui étend SecurityMetadataSource.
Ce que j'ai fait est de créer une classe personnalisée qui implémente FilterInvocationSecurityMetadatasource, OptionsFromDataBaseFilterInvocationsecuritymetadatasource . J'ai utilisé DefaultFilterInvocationSecurityMetadatasource comme base pour utiliser urlMatcher, pour implémenter la méthode support () et quelque chose comme ça.
Ensuite, vous devez implémenter ces méthodes:
-
Collection getAttributes (Object object), où vous pouvez accéder à la base de données, en recherchant l ''objet' sécurisé (normalement L'URL à accéder) pour obtenir les ConfigAttribute autorisés (normalement le Rôle)
-
Support booléen (Classe clazz)
Collection getAllConfigAttributes()
Soyez prudent avec le dernier, car il est appelé au démarrage et peut-être n'est pas bien configuré en ce moment (je veux dire, avec le contexte datasources ou persistence autowired, en fonction de ce que vous utilisez). La solution dans un environnement web consiste à configurer contextConfigLocation dans le web.xml pour charger l'applicationContext.xml avant de le applicationContext-sécurité.xml
La dernière étape consiste à personnaliser applicationContext-security.xml pour charger ce bean.
Pour ce faire, j'ai utilisé des beans réguliers dans ce fichier au lieu de l'espace de noms de sécurité:
<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>
, Vous devez définir tous les haricots. Par exemple:
<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>
Je sais que c'est pas bien expliqué réponse, mais ce n'est pas aussi difficile qu'il y paraît.
Il suffit d'utiliser la source de printemps comme base et vous obtiendrez ce que vous vouloir.
Le débogage avec les données de votre base de données vous aidera beaucoup.
En fait, spring security 3.2 n'encourage pas à le faire selon http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-dynamic-url-metadata
Mais, il est possible (mais pas élégant) d'utiliser l'élément http dans l'espace de noms avec un accessDecisionManager personnalisé..
La configuration doit être:
<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>
Le CustomAccessDecisionManager devrait être...
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();
}
...
}
Où getconfigattributesfromsecureduris récupère les rôles de formulaire DB pour L'URL spécifique
J'ai un peu le même problème, fondamentalement je voudrais garder séparé la liste d'intercept-url de l'autre section de configuration de springsecurity, la première à appartenir à la configuration de l'application la dernière à la configuration du produit (core, plugin).
Il y a une proposition dans la JIRA du printemps, concernant ce problème.
Je ne veux pas abandonner pour utiliser l'espace de noms springsecurity, donc je pensais à quelques solutions possibles afin de traiter ce.
Pour que la liste d'intercept-url soit créée dynamiquement, vous devez injecter l'objet securitymetadatasource dans FilterSecurityInterceptor. Utilisation du schéma springsecurity L'instance de FilterSecurityInterceptor est créée par la classe HttpBuilder et il n'y a aucun moyen de passer la propriété securitymetadatasource as définie dans le fichier de configuration du schéma, moins que d'utiliser une sorte de solution de contournement, qui pourrait être:
- définir un filtre personnalisé à exécuter avant FilterSecurityInterceptor, dans ce filtre, récupérer L'instance FilterSecurityInterceptor (en supposant qu'une section http unique est définie) par le contexte spring et y injecter l'instance securitymetadatasource;
- la même chose que ci-dessus mais dans un HandlerInterceptor.
Qu'en penses-tu?
C'est la solution que j'ai appliquée afin de diviser la liste des entrées intercept-url de l'autre configuration de sécurité 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>
Le bean securityMetadataSource peut être placé soit dans le même fichier de configuration, soit dans un autre fichier de configuration.
<security:filter-security-metadata-source
id="securityMetadataSource" use-expressions="true">
<security:intercept-url pattern="/admin/**"
access="hasRole('ROLE_ADMIN')" />
</security:filter-security-metadata-source>
Bien sûr, vous pouvez décider d'implémenter votre propre bean securityMetadataSource en implémentant L'interface FilterInvocationSecurityMetadatasource. Quelque chose comme ceci:
<bean id="securityMetadataSource" class="mypackage.MyImplementationOfFilterInvocationSecurityMetadataSource" />
J'espère que cela aide.
Une solution simple qui fonctionne pour moi.
<intercept-url pattern="/**/**" access="#{@customAuthenticationProvider.returnStringMethod}" />
<intercept-url pattern="/**" access="#{@customAuthenticationProvider.returnStringMethod}" />
CustomAuthenticationProvider est un bean
<beans:bean id="customAuthenticationProvider"
class="package.security.CustomAuthenticationProvider" />
Dans la classe CustomAuthenticationProvider, créez la méthode:
public synchronized String getReturnStringMethod()
{
//get data from database (call your method)
if(condition){
return "IS_AUTHENTICATED_ANONYMOUSLY";
}
return "ROLE_ADMIN,ROLE_USER";
}
Voici comment cela peut être fait dans 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 */
}
}