Spring Security - Autenticación API basada en tokens y autenticación de usuario / contraseña


Estoy tratando de crear una aplicación web que proporcionará principalmente una API REST utilizando Spring, y estoy tratando de configurar el lado de seguridad.

Estoy tratando de implementar este tipo de patrón: https://developers.google.com/accounts/docs/MobileApps (Google ha cambiado totalmente esa página, así que ya no tiene sentido-ver la página a la que me refería aquí: http://web.archive.org/web/20130822184827/https://developers.google.com/accounts/docs/MobileApps)

Aquí está lo que Necesito acompañar:

  • La aplicación web tiene formularios de inicio de sesión/registro simples que funcionan con la autenticación de usuario/contraseña normal de Spring (ya ha hecho este tipo de cosas antes con dao/authenticationmanager/userdetailsservice, etc.)
  • endpoints de la api REST que son sesiones sin estado y cada solicitud autenticada en función de un token proporcionado con la solicitud

(por ejemplo, inicios de sesión/registros de usuario utilizando formularios normales, webapp proporciona cookie segura con token que luego se puede usar en siguiendo las solicitudes de API)

Tenía una configuración de autenticación normal de la siguiente manera:

@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();
}

Estaba pensando en agregar un filtro pre-auth, que comprueba el token en la solicitud y luego establece el contexto de seguridad (¿eso significaría que la autenticación siguiente normal se omitiría?), sin embargo, más allá del usuario/contraseña normal, no he hecho demasiado con la seguridad basada en tokens, pero basado en algunos otros ejemplos se me ocurrió lo siguiente:

Seguridad Config:

@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();
    }

Mi filtro rest personalizado:

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

Lo anterior en realidad resulta en un error al iniciar la aplicación diciendo: authenticationManager must be specified ¿Puede alguien decirme cuál es la mejor manera de hacer esto - es un filtro pre_auth la mejor manera de hacer esto?


EDITAR

Escribí lo que encontré y cómo lo hice con Spring-security (incluido el código) implementando una implementación de token estándar (no OAuth)

Visión general del problema y enfoque/solución

Implementación de la solución con Spring-security

Espero que ayude a otros..

Author: rhinds, 2014-02-24

1 answers

Creo que el error que mencionas es solo porque la clase base AbstractAuthenticationProcessingFilter que estás usando requiere un AuthenticationManager. Si no va a usarlo, puede configurarlo como no-op, o simplemente implementar Filter directamente. Si su Filter puede autenticar la solicitud y configura el SecurityContext, generalmente se omitirá el procesamiento descendente (depende de la implementación de los filtros descendentes, pero no veo nada extraño en su aplicación, por lo que probablemente todos se comporten de esa manera).

Si yo fuera tú Podría considerar poner los endpoints de la API en una cadena de filtros completamente separada (otro bean WebSecurityConfigurerAdapter). Pero eso solo hace que las cosas sean más fáciles de leer, no necesariamente cruciales.

Es posible que encuentres (como se sugiere en los comentarios) que terminas reinventando la rueda, pero no hay daño en intentarlo, y probablemente aprenderás más sobre la Primavera y la Seguridad en el proceso.

ADICIÓN: el enfoque de github es bastante interesante: los usuarios solo usan el token como contraseña en la autenticación básica, y el servidor no necesita un filtro personalizado (BasicAuthenticationFilter está bien).

 7
Author: Dave Syer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-25 11:03:40