Connexion Google avec l'application de sécurité Java Spring


J'essaie d'intégrer Google sign-in dans une application de sécurité Spring existante. L'objectif est d'avoir une connexion Google touche qui permettra à un utilisateur de se connecter avec la norme connectez-vous en utilisant le nom d'utilisateur/mot de passe.

Basé sur le guide fourni par Google (https://developers.google.com/identity/sign-in/web/backend-auth) il semble que tout ce que je dois faire est d'étendre le formulaire de connexion (qui n'a actuellement que le login et l'entrée de mot de passe fields) avec un champ supplémentaire "id_token" et le soumettre au serveur.

Serait-ce une bonne pratique de sécurité? J'ai cherché sur le Web et je suis surpris de ne pas trouver d'implémentations similaires sur le Web.

Author: Vitaly Zakharenko, 2017-08-15

2 answers

Voici mon point de vue sur les composants spring-security requis:

Filtre:

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.util.Assert;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class GoogleIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static final long serialVersionUID = 1L;
    private String tokenParamName = "googleIdToken";

    /**
     * Creates an instance which will authenticate against the supplied
     * {@code AuthenticationManager} and which will ignore failed authentication attempts,
     * allowing the request to proceed down the filter chain.
     *
     * @param authenticationManager     the bean to submit authentication requests to
     * @param defaultFilterProcessesUrl the url to check for auth requests on (e.g. /login/google)
     */
    public GoogleIdAuthenticationFilter(AuthenticationManager authenticationManager, String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        Assert.notNull(authenticationManager, "authenticationManager cannot be null");
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String token = request.getParameter(tokenParamName);

        if (token == null) {
            return null;
        }

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Google ID Token Authorization parameter found with value '" + token + "'");
        }

        Object details = this.authenticationDetailsSource.buildDetails(request);

        GoogleIdAuthenticationToken authRequest = new GoogleIdAuthenticationToken(token, details);

        Authentication authResult = getAuthenticationManager().authenticate(authRequest);

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success: " + authResult);
        }

        return authResult;
    }

    public String getTokenParamName() {
        return tokenParamName;
    }

    public void setTokenParamName(String tokenParamName) {
        this.tokenParamName = tokenParamName;
    }
}

Fournisseur d'Authentification:

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import javax.annotation.Resource;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

public class GoogleIdAuthenticationProvider implements AuthenticationProvider {
    private static final Logger logger = LoggerFactory.getLogger(GoogleIdAuthenticationProvider.class);

    private String clientId;

    @Resource
    private UserDetailsService userDetailsService;

    private HttpTransport httpTransport = new ApacheHttpTransport();
    private JsonFactory jsonFactory = new JacksonFactory();

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!supports(authentication.getClass())) {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("This authentication provider does not support instances of type %s", authentication.getClass().getName()));
            }
            return null;
        }

        GoogleIdAuthenticationToken googleIdAuthenticationToken = (GoogleIdAuthenticationToken) authentication;

        if (logger.isDebugEnabled())
            logger.debug(String.format("Validating google login with token '%s'", googleIdAuthenticationToken.getCredentials()));


        GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(httpTransport, jsonFactory)
                .setAudience(Collections.singletonList(getClientId()))
                .build();

        GoogleIdToken googleIdToken = null;
        try {
            googleIdToken = verifier.verify((String) googleIdAuthenticationToken.getCredentials());

            if (googleIdToken == null) {
                throw new BadCredentialsException("Unable to verify token");
            }
        } catch (IOException|GeneralSecurityException e) {
            throw new BadCredentialsException("Unable to verify token", e);
        }

        Payload payload = googleIdToken.getPayload();

        // Get profile information from payload
        String email = payload.getEmail();

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Loading user details for email '%s'", email));
        }
        UserDetails userDetails = null;
        try {
            userDetails = userDetailsService.loadUserByUsername(email);

            if (!userDetails.isAccountNonLocked()) {
                throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
            }

            if (!userDetails.isEnabled()) {
                throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
            }

            if (!userDetails.isAccountNonExpired()) {
                throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
            }
        } catch (UsernameNotFoundException e) {
            // provision a new user?
            throw e;
        }

        return new GoogleIdAuthenticationToken((String) googleIdAuthenticationToken.getCredentials(), userDetails.getUsername(), userDetails.getAuthorities(), authentication.getDetails());
    }

    @Override
    public boolean supports(Class<? extends Object> authentication) {
        return (GoogleIdAuthenticationToken.class.isAssignableFrom(authentication));
    }

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }
}

Jeton:

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.ArrayList;
import java.util.Collection;

public class GoogleIdAuthenticationToken extends AbstractAuthenticationToken {
    private String credentials;
    private Object principal;

    public GoogleIdAuthenticationToken(String token, Object details) {
        super(new ArrayList<>());
        this.credentials = token;
        setDetails(details);
        setAuthenticated(false);
    }

    GoogleIdAuthenticationToken(String token, String principal, Collection<? extends GrantedAuthority> authorities, Object details) {
        super(authorities);
        this.credentials = token;
        this.principal = principal;
        setDetails(details);
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

Après avoir branché ce qui précède, vous aurez juste besoin de POSTER sur "/login/google" (ou tout ce que vous avez configuré) avec le jeton retourné par Google dans le 'googleIdToken' (ou tout ce que vous avez configuré).

 2
Author: danw, 2017-10-13 02:20:07

Donc, la bonne réponse s'est avérée ne pas étendre le filtre/fournisseur d'authentification existant mais définir / ajouter un autre {Token Authentication class + token auth filter + token auth provider (le fournisseur est en quelque sorte facultatif)}

 0
Author: Vitaly Zakharenko, 2017-08-20 07:58:50