英文:
AuthorizationCodeInstalledApp, unexpected behaviour
问题
我正在编写一个JavaFX应用程序,其中有一个登录按钮用于获取凭据。当按下按钮时,控制器启动Google身份验证,如果不存在StoredCredential,则会打开浏览器。执行此代码时,应用程序线程会等待,因此应用程序无法响应。我尝试关闭浏览器以查看在这种情况下会发生什么,但应用程序仍然无法响应。
我应该如何解决这个问题?我是否应该在单独的线程中运行授权代码,只有在凭据有效时才继续进行?
英文:
I'm writing a JavaFX application and I have a login button to get the credentials.
When the button is pressed the controller starts the google authentication, this piece of code opens the browser if StoredCredential is not present:
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport
, JSON_FACTORY
,clientSecrets
, scope)
.setAccessType("offline")
.setDataStoreFactory(dataStoreFactory)
.setApprovalPrompt("auto")
.build();
LocalServerReceiver serverReceiver = new LocalServerReceiver.Builder().setPort(8888).build();
credentials = new AuthorizationCodeInstalledApp(flow, serverReceiver).authorize("user");
While this code is executed the app thread is waiting so the app is irresponsive. I tried to close the browser to see what would happen in this case, the app is still irresponsive.
How could I solve it? Should I run the authorization code in a separate thread and proceed only if credentials are valid?
答案1
得分: 0
因此,在进行了一些研究后,我发现这个问题在两年前已经在GitHub上的“Google OAuth Java客户端库”存储库中报告过,分别在这里#175和这里#182。
如建议的那样,我尝试使用JavaFX WebView,但它也存在一些问题,可能是因为它有点过时。我可以获取凭据,但无法访问“已收到验证代码。您现在可以关闭此窗口”页面。
除了WebView之外,还有一些替代方法,例如JxBrowser或JCEF,但第一个价格非常昂贵,后者在JavaFX中不容易实现。
我最终使用了已安装的浏览器,并在单独的线程中运行身份验证流程。登录按钮在90秒内被禁用,同时使用JavaFX Timeline启动倒计时动画。每秒钟检查一次凭据是否有效。在90秒后,时间线停止,如果凭据仍然无效,则检查线程是否仍在运行并停止它。
以下是代码:
GoogleAuthentication authentication = new GoogleAuthentication();
Task<Void> oauthTask = new Task<>() {
@Override
protected Void call() throws Exception {
authentication.init();
return null;
}
};
Thread oauthThread = new Thread(oauthTask);
oauthThread.setDaemon(true);
AtomicInteger waitTime = new AtomicInteger(90);
loginButton.setDisable(true);
loginButton.setText(waitTime + "s");
Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
timeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1), event -> {
if (isValidCredentials(authentication.getCredentials())) {
resetButton(timeline);
System.out.println(authentication.getCredentialsExpiration());
return;
}
waitTime.getAndDecrement();
loginButton.setText(waitTime + "s");
if (waitTime.intValue() <= 0) {
oauthThread.stop();
resetButton(timeline);
}
})
);
timeline.play();
oauthThread.start();
是的,我知道不应该使用Thread.stop()方法,我知道它已经被弃用,但这是唯一让它工作的方法,否则用于身份验证的LocalServerReceiver会抛出此异常“java.net.BindException: Address already in use: bind”。
我尝试使用interrupt(),但线程仍然会继续等待,感谢Google的... "不太好" 实现吧。
更好的解决方案是将LocalServerReceiver保留为实例变量,使用getter方法并使用stop()方法。
因此,这个oauthThread.stop();
变成了authentication.getServerReceiver().stop();
,并放在try\catch IOException中。
这是我目前的解决方案,我必须承认这是我第一次真正使用线程和并发。如果您有任何其他想法或改进,请留下评论。
英文:
So, after doing some research I found that this issue has been reported two years ago on the "Google OAuth Client Library for Java" repository on GitHub, here #175 and here #182.
As suggested I tried to use JavaFX WebView but it has some issues as well, probably because it's a bit obsolete. I could get the credentials but I could not reach the "Received verification code. You may now close this window" page.
There are some alternatives to WebView like JxBrowser or JCEF however the first one is crazy expensive and the latter is not easy to implement in JavaFX.
I ended up using the installed browser and running the authentication flow in a separate thread. The login button is disabled for 90sec and a countdown animation starts using JavaFX Timeline. Every second it checks if credentials are valid. After 90sec the timeline stops and if credentials are still not valid it checks if the Thread is still running and stops it.
Here is the code:
GoogleAuthentication authentication = new GoogleAuthentication();
Task<Void> oauthTask = new Task<>() {
@Override
protected Void call() throws Exception {
authentication.init();
return null;
}
};
Thread oauthThread = new Thread(oauthTask);
oauthThread.setDaemon(true);
AtomicInteger waitTime = new AtomicInteger(90);
loginButton.setDisable(true);
loginButton.setText(waitTime + "s");
Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
timeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1), event -> {
if (isValidCredentials(authentication.getCredentials())) {
resetButton(timeline);
System.out.println(authentication.getCredentialsExpiration());
return;
}
waitTime.getAndDecrement();
loginButton.setText(waitTime + "s");
if (waitTime.intValue() <= 0) {
oauthThread.stop();
resetButton(timeline);
}
})
);
timeline.play();
oauthThread.start();
<s>Yes, I know I shouldn't use the Thread.stop() method, I know it's deprecated, but it's the only way to make it work otherwise the LocalServerReceiver used for the authentication throws this exception "java.net.BindException: Address already in use: bind".
I tried with interrupt() but the thread just keeps waiting, thanks Google for your... "not so good" implementation I guess.</s>
Better solution, keep the LocalServerReceiver as an instance variable, use a getter and use the stop() method.
So this oauthThread.stop();
becomes authentication.getServerReceiver().stop();
surrounded by try\catch IOException
This is my solution so far, I have to admit it's my first time working with threads and concurrency for real. If you have any other ideas or improvements please comment out.
答案2
得分: -1
请看这里:https://docs.oracle.com/javafx/2/webview/jfxpub-webview.htm
有关使用和与弹出窗口交互的文档。
英文:
Take a look here: https://docs.oracle.com/javafx/2/webview/jfxpub-webview.htm
Docs on using and interacting with pop-up windows
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论