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.
This error typically means the Client Redirect URL was not properly added to the OAuth Web Application in the Google Cloud Console. To resolve this, the user will need to copy the Client Redirect URL from the Single Sign-On Settings page from ThinkCentral, my.hrw.com, or HMH Ed.
Google's documentation for "Mobile and Desktop apps" does direct developers to use a PKCE Authorization Code flow. Clients using Google Android, iOS or windows store credential types with PKCE may omit the client_secret (see the note on the refresh token parameter table - and confirmed by Cristiano).
A redirect URI, or reply URL, is the location where the authorization server sends the user once the app has been successfully authorized and granted an authorization code or access token.
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.
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");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With