7

I am working on a Spring-MVC application running on tomcat in which I would like to use Google drive functionality. I tried with a service account on my local machine and I had no problems. But when I uploaded the code on server, the browser URL wont be opened. Then I thought, I should not use a service account, I should use a normal web-application account. Now when I do that, I get a redirect_uri_mismatch.

I don't understand one thing, I am setting the redirect URL in flow, in the JSON, why on earth is it getting the redirect_url with random port numbers. If I change the port number in the browser URL, it works fine. But still on server it wont open the browser url, I can see it in tomcat logs, but the damn thing does not open the URL.

Here are my redirect URL from Google app :

http://localhost/authorizeuser
http://localhost:8080/
http://localhost:8080
http://localhost
http://localhost:8080/Callback
https://testserver.net/Callback
http://testserver.net/Callback
http://127.0.0.1

Here is my client_secret.json :

{"web": {
    "client_id": "clientid",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://accounts.google.com/o/oauth2/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_email": "clientemailstuff",
    "client_x509_cert_url": "certurlstuff",
    "client_secret": "itsasecret",
    "redirect_uris": ["http://localhost:8080/","http://localhost:8080"],
    "javascript_origins": ["https://testserver.net", "http://testserver.net","http://localhost:8080"]
}}

And here is the code where I am trying to authenticate :

 @Override
    public Credential authorize() throws IOException {
        InputStream in =
                DriveQuickstartImpl.class.getResourceAsStream("/client_secret.json");
        GoogleClientSecrets clientSecrets =
                GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

        GoogleAuthorizationCodeFlow flow =
                new GoogleAuthorizationCodeFlow.Builder(
                        HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
                        .setDataStoreFactory(DATA_STORE_FACTORY)
                        .setAccessType("offline")
                        .build();
        flow.newAuthorizationUrl().setState("xyz").setRedirectUri("http://localhost:8080/Callback");
        Credential credential = new AuthorizationCodeInstalledApp(
                flow, new LocalServerReceiver()).authorize("user");

        if(credential!=null && credential.getRefreshToken() != null){
            storeCredentials(credential);
        }
        return credential;
    }

This is majorly pissing me off as I am setting the redirect url, and it is just being ignored and why on earth a browser tab wont be opened when application is deployed on server.

Update Spring problem also fixed, the below code can be used for GoogleDrive authorization on a server with tomcat or others.

@Service
@Transactional
public class GoogleAuthorization{


    @Autowired
    private DriveQuickstart driveQuickstart;

    private static final String APPLICATION_NAME ="APPNAME";

    private static final java.io.File DATA_STORE_DIR = new java.io.File(
            "/home/deploy/store");

    private static FileDataStoreFactory DATA_STORE_FACTORY;

    private static final JsonFactory JSON_FACTORY =
            JacksonFactory.getDefaultInstance();

    private static HttpTransport HTTP_TRANSPORT;

    private static final List<String> SCOPES =
            Arrays.asList(DriveScopes.DRIVE);

    private static final String clientid = "clientid";
    private static final String clientsecret = "clientsecret";

    private static final String CALLBACK_URI = "http://localhost:8080/getgooglelogin";

    private String stateToken;

    private final GoogleAuthorizationCodeFlow flow;

    public GoogleAuthorization(){
        try {
            HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
            DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);

        } catch (GeneralSecurityException | IOException e) {
            e.printStackTrace();
        }

        flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT,
                JSON_FACTORY, clientid, clientsecret, SCOPES).setAccessType("offline").setApprovalPrompt("force").build();
        generateStateToken();

    }



    /**
     * Builds a login URL based on client ID, secret, callback URI, and scope
     */
    public String buildLoginUrl() {

        final GoogleAuthorizationCodeRequestUrl url = flow.newAuthorizationUrl();

        return url.setRedirectUri(CALLBACK_URI).setState(stateToken).build();
    }

    /**
     * Generates a secure state token
     */
    private void generateStateToken(){
        SecureRandom sr1 = new SecureRandom();
        stateToken = "google;"+sr1.nextInt();
    }

    /**s
     * Accessor for state token
     */
    public String getStateToken(){
        return stateToken;
    }

    /**
     * Expects an Authentication Code, and makes an authenticated request for the user's profile information
     * * @param authCode authentication code provided by google
     */
    public void saveCredentials(final String authCode) throws IOException {

        GoogleTokenResponse response = flow.newTokenRequest(authCode).setRedirectUri(CALLBACK_URI).execute();
        Credential credential = flow.createAndStoreCredential(response, null);
        System.out.println(" Credential access token is "+credential.getAccessToken());
        System.out.println("Credential refresh token is "+credential.getRefreshToken());
// The line below gives me a NPE.
        this.driveQuickstart.storeCredentials(credential);
    }
}

Controller method :

  @RequestMapping(value = "/getgooglelogin")
    public String getGoogleLogin(HttpServletRequest request, HttpServletResponse response, HttpSession session,Model model) {
// Below guy should be autowired if you want to use Spring. 
        GoogleAuthorization helper = new GoogleAuthorization();

        if (request.getParameter("code") == null
                || request.getParameter("state") == null) {

            model.addAttribute("URL", helper.buildLoginUrl());
            session.setAttribute("state", helper.getStateToken());

        } else if (request.getParameter("code") != null && request.getParameter("state") != null && request.getParameter("state").equals(session.getAttribute("state"))) {
            session.removeAttribute("state");

            try {
                helper.saveCredentials(request.getParameter("code"));
                return "redirect:/dashboard";
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "newjsp";
    }

newjsp just has a button to click on the URL.

We are Borg
  • 5,117
  • 17
  • 102
  • 225
  • redirect uri is the location of the server sending the request www.whatever.com/callback your production server is not localhost. – Linda Lawton - DaImTo Jun 30 '15 at 11:24
  • @DaImTo : I agree with that, but the redirect uri I have mentioned in the credentials are not called. And there is no way I have found to set redirect url for the request being made, the code is setting it to localhost:randomPortNumber eventhough I have specified in JSON to use localhost:8080. – We are Borg Jun 30 '15 at 11:43
  • The client library detects it for you its sent from the location you are sending from. You should not be setting the redirect URI in your code. you should set the redirect uri you are sending from in the Google Developer console and that's all you need to do. set up your ide so its not creating a random port number – Linda Lawton - DaImTo Jun 30 '15 at 11:45
  • @DaImTo : Ok, but the same thing is happening when we deploy the war file on server running Apache tomcat where no IDE is used. Do you know why when we deploy the war file, the browser URL is not getting opened, while on local machine it is opening, what modifications I need to do to make the server open the URL. Any idea where in IntelliJ idea I can instruct not to call random port numbers for redirect URL. Thanks a lot. I am completely new to Google API's, no idea whats going on. – We are Borg Jun 30 '15 at 11:55
  • Also, as you can see, I have configured the correct port number : http://postimg.org/image/r43qpuqsh/ – We are Borg Jun 30 '15 at 11:58
  • @DaImTo : Any ideas?? – We are Borg Jul 01 '15 at 11:00
  • I cant help much I am not a Java programing I am a .Net programmer Visual studio does the exact same thing. – Linda Lawton - DaImTo Jul 01 '15 at 11:03
  • @DaImTo : What do you mean by the exact same thing, not open the browser URL when deployed on server you mean? – We are Borg Jul 01 '15 at 11:03
  • Its probably not opening it because its sending the authentication to your localhost instead of the server. basically its confused. – Linda Lawton - DaImTo Jul 01 '15 at 11:05
  • No, the url itself wont open to authenticate, but the URL is getting printed in the console which I can open manually and use normally. – We are Borg Jul 01 '15 at 11:06

2 Answers2

2

Specifically, you're getting random ports because you are using LocalServerReceiver, which starts up a jetty instance on a free port in order to receive an auth code.

At a higher level, it looks like you are developing a web server application, but you are trying to use Google OAuth as if it were an installed application. If you are indeed making a web server application, you should be using your server's host name instead of localhost in your callback URL, providing a link for the end user to authenticate using flow.newAuthorizationUrl(), and have your callback fetch the token using flow.newTokenRequest(String). Also make sure that the Client ID you created in your console is of type Web application, or you'll get redirect_uri_mismatch errors. A full working example of how to do this can be found here.

Community
  • 1
  • 1
heenenee
  • 19,914
  • 1
  • 60
  • 86
  • I will do the changes now and get back to you. Thanks a lot. – We are Borg Jul 08 '15 at 07:35
  • My friend, everything seems to work, just the class is not connecting to Spring. That is why I am getting a NPE. I am editing my main post, can you please check out what I can do. – We are Borg Jul 08 '15 at 08:57
  • I have just edited my post. Can you please check the GoogleAuthorization class I have, in the last method, I am getting a NPE because Spring is not autowiring properly – We are Borg Jul 08 '15 at 09:04
  • 1
    So, The issue was resolved, we are testing it, once done, I will mark your answer. Thanks a lot again. Kudos. – We are Borg Jul 08 '15 at 11:09
2

Instead of using:

Credential credential = new AuthorizationCodeInstalledApp( flow, 
                     new LocalServerReceiver).authorize("user");

Use

LocalServerReceiver localReceiver = new LocalServerReceiver.
                                        Builder().setPort(XXXX).build();

for setting a static port number

Credential credential = new AuthorizationCodeInstalledApp( flow,
                                      localReceiver).authorize("user");

although you wont be able to change redirect url, however you can set the host as well as port. For changing host use .setHost() method

You can also use default constructor as:

Credential credential = new AuthorizationCodeInstalledApp( flow, 
             new LocalServerReceiver("Host", XXXX).authorize("user");
ρяσѕρєя K
  • 132,198
  • 53
  • 198
  • 213