Java桌面应用中如何从其他线程取消Google OAuth2授权?
我完全理解你遇到的这个棘手问题——当用户关闭浏览器放弃授权时,卡在Condition#awaitUninterruptibly里的授权线程怎么都停不下来,LocalServerReceiver.stop()没触发唤醒,普通线程中断也因为awaitUninterruptibly而失效。结合你使用的com.google.oauth-client:google-oauth-client-jetty:1.22.0版本,我给你两个可行的解决方案:
方案一:自定义可取消的LocalServerReceiver子类
问题的核心在于原生LocalServerReceiver的stop()方法只关闭了Jetty服务器,但没有唤醒等待授权码的Condition。我们可以通过继承它,利用反射获取内部的Condition对象,在stop()时主动唤醒等待线程:
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; import java.io.IOException; import java.lang.reflect.Field; import java.util.concurrent.locks.Condition; public class CancelableLocalServerReceiver extends LocalServerReceiver { private final Condition waitCondition; public CancelableLocalServerReceiver(Builder builder) throws IOException { super(builder); // 通过反射获取原类中私有Condition字段 try { Field conditionField = LocalServerReceiver.class.getDeclaredField("condition"); conditionField.setAccessible(true); this.waitCondition = (Condition) conditionField.get(this); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException("Failed to access condition field", e); } } @Override public void stop() throws IOException { super.stop(); // 唤醒所有等待授权码的线程 waitCondition.signalAll(); } }
然后替换你原有代码中的LocalServerReceiver创建逻辑:
LocalServerReceiver receiver = new CancelableLocalServerReceiver( new LocalServerReceiver.Builder().setPort(8888).build() );
现在当你从其他线程调用receiver.stop()时,不仅会关闭本地服务器,还会主动唤醒卡在waitForCode()里的授权线程,让授权流程正常终止。
方案二:手动接管授权流程(避免依赖awaitUninterruptibly)
如果你不想用反射,也可以手动实现授权流程的关键步骤,绕过原生AuthorizationCodeInstalledApp的阻塞逻辑:
- 调用
flow.newAuthorizationUrl()生成授权URL并打开浏览器 - 自己启动一个简单的HTTP服务器监听回调(或者复用Jetty)
- 用一个可中断的等待机制(比如
CountDownLatch结合定时检查)代替awaitUninterruptibly - 当用户取消时,直接中断等待并关闭HTTP服务器
这个方案更灵活,但需要写更多代码,适合对反射有顾虑的场景。
补充说明
原生LocalServerReceiver的设计缺陷在于,stop()方法没有处理等待线程的唤醒逻辑,而waitForCode()用了awaitUninterruptibly,导致普通线程中断无法生效。上面的方案一通过反射弥补了这个缺陷,是最贴合你现有代码的快速修复方式。
内容的提问来源于stack exchange,提问作者dan1st

