web-dev-qa-db-fra.com

Spring Security - Authentification API basée sur les jetons et authentification utilisateur / mot de passe

J'essaie de créer une application Web qui fournira principalement une API REST) à l'aide de Spring et tente de configurer le côté sécurité.

J'essaie d'implémenter ce type de modèle: https://developers.google.com/accounts/docs/MobileApps (Google a totalement changé cette page, elle n'a donc plus de sens - voir la page I faisait référence à: http://web.archive.org/web/20130822184827/https://developers.google.com/accounts/docs/MobileApps )

Voici ce que je dois accompagner:

  • L'application Web comporte de simples formulaires de connexion/inscription fonctionnant avec une authentification utilisateur/mot de passe normale (vous avez déjà effectué ce type de tâche avec dao/authenticationmanager/userdetailsservice, etc.).
  • Les points de terminaison api REST qui sont des sessions sans état et chaque demande authentifiée sur la base d'un jeton fourni avec la demande

(par exemple, connexions utilisateur/inscription à l'aide de formulaires normaux, webapp fournit un cookie sécurisé avec un jeton qui peut ensuite être utilisé dans les demandes d'API suivantes)

J'ai eu une configuration d'authentification normale comme ci-dessous:

@Override protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf()
            .disable()
        .authorizeRequests()
            .antMatchers("/resources/**").permitAll()
            .antMatchers("/mobile/app/sign-up").permitAll()
            .antMatchers("/v1/**").permitAll()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/")
            .loginProcessingUrl("/loginprocess")
            .failureUrl("/?loginFailure=true")
            .permitAll();
}

Je pensais ajouter un filtre de pré-autorisation, qui vérifie la présence du jeton dans la demande, puis définit le contexte de sécurité (cela voudrait-il dire que l'authentification suivante sera ignorée?). Cependant, au-delà de mon utilisateur/mot de passe normal pas trop en ce qui concerne la sécurité basée sur les jetons, mais en me basant sur d’autres exemples, j’ai proposé ce qui suit:

Configuration de sécurité:

@Override protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .disable()
            .addFilter(restAuthenticationFilter())
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and()
                .antMatcher("/v1/**")
            .authorizeRequests()
                .antMatchers("/resources/**").permitAll()
                .antMatchers("/mobile/app/sign-up").permitAll()
                .antMatchers("/v1/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/")
                .loginProcessingUrl("/loginprocess")
                .failureUrl("/?loginFailure=true")
                .permitAll();
    }

Mon filtre de repos personnalisé:

public class RestAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public RestAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    private final String HEADER_SECURITY_TOKEN = "X-Token"; 
    private String token = "";


    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        this.token = request.getHeader(HEADER_SECURITY_TOKEN);

        //If we have already applied this filter - not sure how that would happen? - then just continue chain
        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
            return;
        }

        //Now mark request as completing this filter
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

        //Attempt to authenticate
        Authentication authResult;
        authResult = attemptAuthentication(request, response);
        if (authResult == null) {
            unsuccessfulAuthentication(request, response, new LockedException("Forbidden"));
        } else {
            successfulAuthentication(request, response, chain, authResult);
        }
    }

    /**
     * Attempt to authenticate request - basically just pass over to another method to authenticate request headers 
     */
    @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        AbstractAuthenticationToken userAuthenticationToken = authUserByToken();
        if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
        return userAuthenticationToken;
    }


    /**
     * authenticate the user based on token, mobile app secret & user agent
     * @return
     */
    private AbstractAuthenticationToken authUserByToken() {
        AbstractAuthenticationToken authToken = null;
        try {
            // TODO - just return null - always fail auth just to test spring setup ok
            return null;
        } catch (Exception e) {
            logger.error("Authenticate user by token error: ", e);
        }
        return authToken;
    }

Ce qui précède entraîne en réalité une erreur au démarrage de l'application en disant: authenticationManager must be specified Quelqu'un peut-il me dire comment le faire au mieux - un filtre de pré-authentification est-il le meilleur moyen de le faire?


[~ # ~] éditer [~ # ~]

J'ai écrit ce que j'ai trouvé et comment je l'ai fait avec Spring-security (y compris le code) en implémentant une implémentation de jeton standard (pas OAuth)

Vue d'ensemble du problème et approche/solution

Implémentation de la solution avec Spring-security

J'espère que ça aide les autres ..

44
rhinds

Je crois que l'erreur que vous mentionnez est simplement due au fait que la classe de base AbstractAuthenticationProcessingFilter que vous utilisez nécessite un AuthenticationManager. Si vous ne l'utilisez pas, vous pouvez le définir sur une opération n °-op ou simplement implémenter Filter directement. Si votre Filter peut authentifier la demande et configurer le SecurityContext, le traitement en aval sera généralement ignoré (cela dépend de la mise en œuvre des filtres en aval, mais je ne vois rien de bizarre dans votre application, donc ils se comportent probablement tous de cette façon).

Si j'étais vous, je pourrais envisager de placer les points de terminaison de l'API dans une chaîne de filtrage complètement séparée (un autre bean WebSecurityConfigurerAdapter.). Mais cela ne fait que rendre les choses plus faciles à lire, pas nécessairement cruciales.

Vous constaterez peut-être (comme le suggèrent les commentaires) que vous finissez par réinventer la roue, mais que vous essayez de rien, et que vous en apprendrez davantage sur Spring et la sécurité.

ADDITION: le approche github est très intéressant: les utilisateurs utilisent simplement le jeton comme mot de passe dans l’authentification de base, et le serveur n’a pas besoin de filtre personnalisé (BasicAuthenticationFilter, ça va).

7
Dave Syer