OAuth2.0作为主流的开放授权框架,在Android平台落地时面临和Web端截然不同的信任环境。手机上的APK文件可以被轻易反编译,任何写死在代码里的密钥都不再保密。因此Android端实现OAuth2.0的核心矛盾在于:标准授权码模式依赖client_secret,而移动客户端根本无法安全保管secret。理解这一边界,是设计正确集成方案的前提。

为什么传统授权码模式在Android端会失效
在Web服务中,后端服务器持有client_secret,用户登录后浏览器拿到授权码code,再由服务端拿code和secret去令牌端点换access_token,整个过程secret不暴露给浏览器。但Android应用如果直接以内嵌WebView或原生请求去换令牌,就必须把client_secret打包进APK,否则授权服务器会拒绝发放令牌。
攻击者只需用apktool反编译资源,搜索字符串或字节码就能提取secret。一旦泄露,对方可以冒充你的应用获取用户授权,甚至刷新长期令牌。更严重的是,很多开发者把access_token也明文存进SharedPreferences,手机 rooted 后数据直接可读。这种实现从安全模型上就违背了OAuth2.0的设计假设。
另一种常见误区是使用隐式模式(implicit),它把令牌直接通过URL fragment返回给客户端,看似省去了secret交换,却让令牌出现在重定向地址、系统日志和浏览器历史中,风险更高。因此IETF已明确不建议移动端使用隐式模式,而推荐扩展的授权码加PKCE。
基于PKCE的授权码模式落地步骤
PKCE(Proof Key for Code Exchange)让公开客户端在不使用secret的情况下防止授权码被截获。其核心是客户端先生成随机code_verifier,算出code_challenge发给授权服务器;换令牌时携带原始verifier,服务器校验一致性才发令牌。即便攻击者拿到code,没有verifier也无法兑换。
在Android中推荐使用Custom Tabs而非WebView发起授权,因为Custom Tabs复用系统浏览器的安全上下文,用户能直观看到网址避免钓鱼。下面代码展示用AuthorizationRequest构造带PKCE的参数:
// 生成PKCE参数
String codeVerifier = generateRandomString(64);
String codeChallenge = sha256Base64(codeVerifier);
AuthorizationRequest req = new AuthorizationRequest.Builder(
"your_client_id",
ResponseType.CODE,
Uri.parse("com.example.app://oauth/callback"))
.setCodeChallenge(codeChallenge)
.setCodeChallengeMethod("S256")
.build();
// 用Custom Tabs打开
CustomTabsIntent tabs = new CustomTabsIntent.Builder().build();
tabs.launchUrl(context, req.toUri());
授权服务器回调到com.example.app://oauth/callback后,Android端从URI取出code,再向令牌端点发起POST。此时请求体包含code、code_verifier、client_id和redirect_uri,不需要secret。建议令牌请求发往自己的后端中转,由后端附加保密的client_secret(如果授权服务器仍要求),这样即使移动端流量被抓包,攻击者也缺secret和verifier两者之一。
拿到access_token后必须存入Android Keystore或EncryptedSharedPreferences,利用硬件级加密确保磁盘静态安全。不要图省事用普通SharedPreferences,那等于把钥匙放在门垫下。每次调用API在Authorization头带Bearer令牌即可,注意设置合理的过期与刷新策略。
常见错误与合规检查清单
不少团队为了赶进度让App直接请求令牌端点并硬编码secret,这种写法在渗透测试中基本一击即破。还有人把redirect_uri设成通配符,导致授权码可被任意应用接收。正确的redirect_uri必须在授权服务器后台精确注册,且使用私有Scheme或HTTPS断言化回调。
以下表格列出Android端OAuth2.0实现的关键检查项:
| 检查点 | 错误做法 | 合规做法 |
|---|---|---|
| client_secret | 写进APK字符串 | 仅存于后端或用PKCE免secret |
| 授权发起 | 内嵌WebView | 系统浏览器或Custom Tabs |
| 令牌存储 | SharedPreferences明文 | EncryptedSharedPreferences |
| redirect_uri | 通配符匹配 | 精确注册私有Scheme |
此外要警惕授权码重放攻击:每次授权应绑定state参数并一次性消费,后端需校验state与session对应。Android端还应处理用户取消、网络异常等边界,避免死循环拉起浏览器。只有把上述环节都串起来,才算在移动环境里真正落实了OAuth2.0的授权精神,而不是只抄了个登录界面。