Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google sign-in with Java Spring security application

I am trying to integrate Google sign-in into an existing Spring security application. The goal is to have a Google sign-in button that will allow a user to log in along with the standard login using the username/password combination.

Based on the guide that Google provides (https://developers.google.com/identity/sign-in/web/backend-auth) it looks like all I need to do is extend the login form (that currently only has the login and the password input fields) with an extra field "id_token" and submit it to the server.

Would it be a good security practice? I searched the web and I am surprised I cannot find any similar implementations on the web.

like image 428
Vitaly Zakharenko Avatar asked Feb 06 '26 17:02

Vitaly Zakharenko


1 Answers

Here is my take on the required spring-security components:

filter:

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

authentication provider:

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

token:

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

After plugging in the above you'll just need to POST to "/login/google" (or whatever you've configured) with the token returned by Google in the 'googleIdToken' (or whatever you've configured).

like image 168
danw Avatar answered Feb 09 '26 10:02

danw



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!