Spring Boot + Spring Security + Spring OAuth2 + Google Sign in Spring Boot + Spring Security + Spring OAuth2 + Google Sign in spring spring

Spring Boot + Spring Security + Spring OAuth2 + Google Sign in


Thank you Cristian, you have no idea how much your code helped to start a foundation for my own code. I modified your original OAuth2 Github project and change it to the following code.

GoogleOAuth2Filter.java

package tech.aabo.celulascontentas.oauth.filter;import static java.lang.Math.toIntExact;import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl;import com.google.api.client.auth.oauth2.TokenResponse;import com.google.api.client.auth.oauth2.TokenResponseException;import com.google.api.client.googleapis.auth.oauth2.*;import com.google.api.client.http.HttpTransport;import com.google.api.client.http.javanet.NetHttpTransport;import com.google.api.client.json.JsonFactory;import com.google.api.client.json.jackson2.JacksonFactory;import com.google.api.services.plus.Plus;import com.google.api.services.plus.model.Person;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.configurationprocessor.json.JSONException;import org.springframework.boot.configurationprocessor.json.JSONObject;import org.springframework.core.io.ClassPathResource;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.context.SecurityContext;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.oauth2.client.OAuth2RestTemplate;import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import tech.aabo.celulascontentas.oauth.domain.User;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.FileReader;import java.io.IOException;import java.io.PrintWriter;import java.math.BigInteger;import java.sql.Timestamp;import java.time.Instant;import java.util.Arrays;import java.util.Calendar;import java.util.UUID;/** * Created by colorado on 9/03/17. * Modified by frhec on 7/06/18 */public class GoogleOAuth2Filter extends AbstractAuthenticationProcessingFilter {/** * Logger */private static final Logger logger = LoggerFactory.getLogger(GoogleOAuth2Filter.class);public GoogleOAuth2Filter(String defaultFilterProcessesUrl) {    super(defaultFilterProcessesUrl);}@Autowired@Overridepublic void setAuthenticationManager(AuthenticationManager authenticationManager) {    super.setAuthenticationManager(authenticationManager);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {    String CLIENT_SECRET_FILE = "client_secret.json";    SecurityContext context = SecurityContextHolder.getContext();    if(context.getAuthentication() == null) {        GoogleClientSecrets clientSecrets = loadSecret(CLIENT_SECRET_FILE);        if (StringUtils.isEmpty(request.getQueryString())) {            try {                GoogleAuthorizationCodeRequestUrl auth = new GoogleAuthorizationCodeRequestUrl(clientSecrets.getDetails().getClientId(),                        request.getRequestURL().toString(), Arrays.asList(                        "https://www.googleapis.com/auth/plus.login",                        "https://www.googleapis.com/auth/plus.me",                        "https://www.googleapis.com/auth/plus.profile.emails.read")).setState("/user");                auth.setAccessType("offline");                response.addHeader("Place","Before");                response.sendRedirect(auth.build());            } catch (IOException e) {                e.printStackTrace();            }        } else {            response.addHeader("Place","After");            AuthorizationCodeResponseUrl authResponse = new AuthorizationCodeResponseUrl(transformName(request, 0));            // check for user-denied error            if (authResponse.getError() != null) {                logger.info("Denied");            } else {                try {                    assert clientSecrets != null;                    Calendar calendar = Calendar.getInstance();                    NetHttpTransport net = new NetHttpTransport();                    JacksonFactory jackson = new JacksonFactory();                    GoogleTokenResponse tokenResponse =                            new GoogleAuthorizationCodeTokenRequest(net, jackson,                                    clientSecrets.getDetails().getClientId(), clientSecrets.getDetails().getClientSecret(),                                    authResponse.getCode(), transformName(request, 1))                                    .execute();                    // Use access token to call API                    GoogleCredential credential;                    if (tokenResponse.getRefreshToken() == null) {                        credential = new GoogleCredential();                        credential.setFromTokenResponse(tokenResponse);                    } else {                        credential = createCredentialWithRefreshToken(net, jackson, clientSecrets, tokenResponse);                    }                    Plus plus =                            new Plus.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)                                    .setApplicationName("Google Plus Profile Info")                                    .build();                    Person profile = plus.people().get("me").execute();                    // Get profile info from ID token                    GoogleIdToken idToken = tokenResponse.parseIdToken();                    GoogleIdToken.Payload payload = idToken.getPayload();                    User auth = new User();                    auth.setAccessToken(tokenResponse.getAccessToken());                    auth.setId(new BigInteger(payload.getSubject().trim())); // Use this value as a key to identify a user.                    auth.setUuid(UUID.randomUUID().toString());                    auth.setEmail(payload.getEmail());                    auth.setVerifiedEmail(payload.getEmailVerified());                    auth.setName(profile.getDisplayName());                    auth.setPictureURL(profile.getImage().getUrl());                    auth.setLocale(profile.getLanguage());                    auth.setFamilyName(profile.getName().getFamilyName());                    auth.setGivenName(profile.getName().getGivenName());                    auth.setStatus(true);                    auth.setExpired(false);                    auth.setLocked(false);                    auth.setExpiredCredentials(false);                    auth.setRoles("USER");                    auth.setRefreshToken(tokenResponse.getRefreshToken());                    auth.setDateCreated(calendar.getTime());                    calendar.add(Calendar.SECOND, toIntExact(tokenResponse.getExpiresInSeconds()));                    auth.setExpirationDate(calendar.getTime());                    auth.setDateModified(Calendar.getInstance().getTime());                    Authentication authenticationToken = getOAuth2Token(auth);                    request.authenticate(response);                    if (//Validation happening) {                        authenticationToken.setAuthenticated(true);                    } else {                        authenticationToken.setAuthenticated(false);                    }                    return authenticationToken;                } catch (TokenResponseException e) {                    if (e.getDetails() != null) {                        System.err.println("Error: " + e.getDetails().getError());                        if (e.getDetails().getErrorDescription() != null) {                            System.err.println(e.getDetails().getErrorDescription());                        }                        if (e.getDetails().getErrorUri() != null) {                            System.err.println(e.getDetails().getErrorUri());                        }                    } else {                        System.err.println(e.getMessage());                    }                } catch (IOException | ServletException e) {                    e.printStackTrace();                }            }        }    }else if(!context.getAuthentication().isAuthenticated()) {        setResponseUnauthenticated(response);    }else{        try {            response.sendRedirect(transformName(request,2)+"/user");        } catch (IOException e) {            e.printStackTrace();        }    }    return null;}private void setResponseUnauthenticated(HttpServletResponse response){    try {        response.setContentType("application/json");        response.setCharacterEncoding("utf-8");        PrintWriter out = response.getWriter();        //create Json Object        JSONObject values = new JSONObject();        values.put("principal", null);        values.put("authentication", null);        values.put("timestamp", String.valueOf(Timestamp.from(Instant.now())));        values.put("code",401);        values.put("message", "Not Authorized");        out.print(values.toString());    } catch (JSONException | IOException e) {        e.printStackTrace();    }}public static GoogleCredential createCredentialWithRefreshToken(HttpTransport transport,                                                                JsonFactory jsonFactory,                                                                GoogleClientSecrets clientSecrets,                                                                TokenResponse tokenResponse) {    return new GoogleCredential.Builder().setTransport(transport)            .setJsonFactory(jsonFactory)            .setClientSecrets(clientSecrets)            .build()            .setFromTokenResponse(tokenResponse);}public static String transformName(HttpServletRequest request, Integer type){    switch(type) {        case 0:            return request.getScheme() + "://" +   // "http" + "://                    request.getServerName() +       // "myhost"                    ":" +                           // ":"                    request.getServerPort() +       // "8080"                    request.getRequestURI() +       // "/people"                    "?" +                           // "?"                    request.getQueryString();       // "lastname=Fox&age=30"        case 1:            return request.getScheme() + "://" +   // "http" + "://                    request.getServerName() +       // "myhost"                    ":" +                           // ":"                    request.getServerPort() +       // "8080"                    request.getRequestURI();      // "/people"        case 2:            return request.getScheme() + "://" +   // "http" + "://                    request.getServerName() +       // "myhost"                    ":" +                           // ":"                    request.getServerPort();        // "8080"        default:            return request.getScheme() + "://" +   // "http" + "://                    request.getServerName() +       // "myhost"                    ":" +                           // ":"                    request.getServerPort() +       // "8080"                    request.getRequestURI() +       // "/people"                    "?" +                           // "?"                    request.getQueryString();       // "lastname=Fox&age=30"    }}@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {    SecurityContextHolder.getContext().setAuthentication(authResult);    // Fire event    if (this.eventPublisher != null) {        eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(                authResult, this.getClass()));    }    response.sendRedirect(transformName(request,2)+"/user");}private CustomOAuth2AuthenticationToken getOAuth2Token(User auth) {    return new CustomOAuth2AuthenticationToken(auth);}private GoogleClientSecrets loadSecret(String name){    ClassPathResource resource = new ClassPathResource(name);    try {        // Exchange auth code for access token        return GoogleClientSecrets.load(JacksonFactory.getDefaultInstance(), new FileReader(resource.getFile()));    } catch (IOException e) {        return null;    }}}

Also I changed the main Security class to:

private GoogleOAuth2Filter googleOAuth2Filter = new GoogleOAuth2Filter("/login/google");@Overrideprotected void configure(HttpSecurity http) throws Exception {     // @formatter:off     http.antMatcher("/**")            .authorizeRequests()               .antMatchers("/", "/login/google", "/error**").permitAll().anyRequest().authenticated()             .and().exceptionHandling().authenticationEntryPoint((request, response, e) -> {                 //create Json Object                 try {                      JSONObject values = new JSONObject();                      values.put("principal", JSONObject.NULL);                      values.put("authentication", JSONObject.NULL);                      values.put("timestamp", String.valueOf(Timestamp.from(Instant.now())));                      values.put("code",401);                      values.put("message", "Not Authorized");                      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);                      response.setContentType("application/json");                      response.setCharacterEncoding("UTF-8");                      response.getWriter().write(values.toString());                  } catch (JSONException | IOException f) {                     f.printStackTrace();                  }                })            .and().addFilterBefore(googleOAuth2Filter, BasicAuthenticationFilter.class);        // @formatter:on}

Also I created custom mappings for /user and /logout.

Hope it can help someone in the future


Things get a lot easier if you use the EnableOAuth2Sso method (though it hides a lot of the process from you). The Spring Boot tutorial on OAuth2 is quite thorough for this, and there are other examples online that I cribbed from (eg https://github.com/SoatGroup/spring-boot-google-auth/ and http://dreamix.eu/blog/java/configuring-google-as-oauth2-authorization-provider-in-spring-boot) that helped a little. Ultimately, this was the resource that helped me the most - covering the whole process and integration client side apps.

If you want to do this at a lower level, there is a lot of detail about the whole process and how it works in Spring on a Pivotal blog post.