web-dev-qa-db-fra.com

Connexion / déconnexion REST avec Spring 3

Nous développons des services Web RESTful avec Spring 3 et nous devons avoir la fonctionnalité de connexion/déconnexion, quelque chose comme /webservices/login/<username>/<password>/ et /webservices/logout. La session doit être stockée dans le contexte jusqu'à ce que la session soit expirée ou déconnectée pour permettre la consommation d'autres services Web. Toute demande d'accès à des services Web sans informations de session doit être rejetée. Vous recherchez une solution de pointe pour ce scénario.

Je ressuscite en fait la question posée ici connexion par programmation de Spring Security , qui n'est toujours pas correctement répondue. Veuillez également spécifier les modifications nécessaires dans web.xml.

21
FKhan

Je suggérerais de définir vos filtres Spring Security complètement manuellement. Ce n'est pas si difficile, et vous obtenez un contrôle total sur votre comportement de connexion/déconnexion.

Tout d'abord, vous aurez besoin d'un texte de présentation web.xml standard pour déléguer la gestion de la chaîne de filtrage à Spring (supprimez la prise en charge async si vous n'êtes pas sur l'API Servlet version 3):

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <async-supported>true</async-supported>
    <filter-class>
        org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
</filter>



<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Maintenant, dans un contexte de sécurité, vous allez définir des filtres séparément pour chaque chemin. Les filtres peuvent authentifier l'utilisateur, déconnecter l'utilisateur, vérifier les informations de sécurité, etc.

<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
    <sec:filter-chain-map path-type="ant">
        <sec:filter-chain pattern="/login" filters="sif,wsFilter"/>
        <sec:filter-chain pattern="/logout" filters="sif,logoutFilter" />
        <sec:filter-chain pattern="/rest/**" filters="sif,fsi"/>
    </sec:filter-chain-map>
</bean>

Le code XML ci-dessus indique à Spring de transmettre les demandes à des URL spécifiques au contexte via des chaînes de filtres. La première chose dans l'une des chaînes de filtrage est d'établir un contexte de sécurité - le bean "sif" s'en charge.

<bean id="sif" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>

Le filtre suivant dans la chaîne peut désormais soit ajouter des données au contexte de sécurité (lire: se connecter/se déconnecter de l'utilisateur), soit décider d'autoriser ou non l'accès en fonction dudit contexte de sécurité.

Pour votre URL de connexion, vous souhaiterez un filtre qui lit les données d'authentification de la demande, les valide et les stocke à son tour dans un contexte de sécurité (qui est stocké dans la session):

<bean id="wsFilter" class="my.own.security.AuthenticationFilter">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="authenticationSuccessHandler" ref="myAuthSuccessHandler"/>
  <property name="passwordParameter" value="pass"></property>
  <property name="usernameParameter" value="user"></property>
  <property name="postOnly" value="false"></property>

Vous pouvez utiliser Spring générique UsernamePasswordAuthenticationFilter mais la raison pour laquelle j'utilise ma propre implémentation est de continuer le traitement de la chaîne de filtrage (l'implémentation par défaut suppose que l'utilisateur sera redirigé en cas d'authentification réussie et termine la chaîne de filtrage), et de pouvoir traiter l'authentification à chaque fois le nom d'utilisateur et le mot de passe lui sont transmis:

public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
    return ( StringUtils.hasText(obtainUsername(request)) && StringUtils.hasText(obtainPassword(request)) );
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
        Authentication authResult) throws IOException, ServletException{
    super.successfulAuthentication(request, response, chain, authResult);
    chain.doFilter(request, response);
}

Vous pouvez ajouter n'importe quel nombre de vos propres implémentations de filtre pour/chemin d'accès de connexion, telles que l'authentification à l'aide de l'en-tête d'authentification HTTP de base, de l'en-tête de résumé ou même extraire le nom d'utilisateur/pwd du corps de la demande. Spring fournit un tas de filtres pour cela.

J'ai mon propre gestionnaire de réussite d'authentification qui remplace la stratégie de redirection par défaut:

public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

   @PostConstruct
   public void afterPropertiesSet() {
       setRedirectStrategy(new NoRedirectStrategy());
   }

    protected class NoRedirectStrategy implements RedirectStrategy {

        @Override
        public void sendRedirect(HttpServletRequest request,
                HttpServletResponse response, String url) throws IOException {
            // no redirect

        }

    }

}

Vous n'avez pas besoin d'avoir un gestionnaire de réussite d'authentification personnalisé (et probablement un filtre d'authentification personnalisé également) si vous êtes d'accord avec la redirection de l'utilisateur après une connexion réussie (l'URL de redirection peut être personnalisée, consultez les documents)

Définissez le gestionnaire d'authentification qui sera responsable de la récupération des détails de l'utilisateur:

<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="myAuthAuthProvider"/>
</sec:authentication-manager>

 <bean id="myAuthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
    <property name="preAuthenticatedUserDetailsService">
        <bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
            <property name="userDetailsService" ref="myUserDetailsImpl"/>
        </bean>
    </property>
</bean>

Vous devrez fournir votre propre implémentation du bean de détails utilisateur ici.

Filtre de déconnexion: responsable de l'effacement du contexte de sécurité

<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    <constructor-arg>
        <list>
            <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
        </list>
    </constructor-arg>
</bean>

Trucs d'authentification générique:

<bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
    <property name="allowIfAllAbstainDecisions" value="false"/>
    <property name="decisionVoters">
        <list>
            <ref bean="roleVoter"/>
        </list>
    </property>
</bean>

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>

<bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>

Filtre de contrôle d'accès (devrait être explicite):

<bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="myAuthenticationManager"/>
    <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
    <property name="securityMetadataSource">
        <sec:filter-invocation-definition-source>
            <sec:intercept-url pattern="/rest/**" access="ROLE_REST"/>
        </sec:filter-invocation-definition-source>
    </property>
</bean>

Vous devriez également pouvoir sécuriser vos services REST avec @Secured annotations sur les méthodes.

Le contexte ci-dessus a été extrait de la webapp de service REST existante - désolé pour d'éventuelles fautes de frappe.

Il est également possible de faire au moins la plupart de ce qui est implémenté ici en utilisant des balises stock sec Spring, mais je préfère une approche personnalisée car cela me donne le plus de contrôle.

J'espère que cela vous aidera au moins à démarrer.

50
rootkit