如何在 iOS Dwolla payment SDK 中获取 access token
结论
[self hasCode:urlValues] 始终返回 false,说明当前拦截到的 URL 查询参数里没有名为 code 的 OAuth 授权码。常见原因包括:
- Dwolla 应用中配置的
redirect_uri与授权请求中的地址不完全一致; - 当前拦截到的是登录过程中的中间页面,不是最终回调 URL;
- Dwolla 返回的是
error,而不是code; - 回调参数的解析方式不够可靠;
code不在第一个参数中,但代码仍固定读取urlValues[0][1]。
应为应用配置明确的回调地址,只在 URL 命中该地址时读取 code,并按参数名查找,不能假定它一定是第一个参数。使用授权码换取 access token 的操作应在服务端完成,不要把 Client secret 写进 iOS 应用。
为什么 hasCode: 一直返回 false
OAuth 登录成功后,Dwolla 应将浏览器重定向到类似下面的地址:
https://example.com/oauth/dwolla/callback?code=AUTHORIZATION_CODE
只有最终回调 URL 中包含 code 参数,hasCode: 才可能返回 YES。可以先打印实际访问的 URL:
NSLog(@"Dwolla navigation URL: %@", req.URL.absoluteString);
如果打印出来的是登录页、授权确认页或其他中间地址,那么没有 code 很正常,WebView 应继续加载。
如果回调地址类似:
https://example.com/oauth/dwolla/callback?error=access_denied
说明用户拒绝了授权,或者授权请求无效,此时同样不会出现 code。
原代码还有一个明显的问题。即使 hasCode: 在数组的其他位置找到了 code,后续代码仍会固定读取第一个参数:
[[urlValues objectAtIndex:0] objectAtIndex:1]
例如回调地址是:
https://example.com/callback?state=abc&code=xyz
第一个参数为 state=abc,这段代码最终会取得 abc,而不是授权码 xyz。
正确解析回调 URL
不要手动使用 & 和 = 拆分查询字符串。这种方法无法可靠处理参数顺序、百分号编码、空值,以及参数值本身包含 = 的情况。可以改用 NSURLComponents 和 NSURLQueryItem:
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *URL = request.URL;
// 必须与创建授权请求时使用的 redirect_uri 对应
BOOL isCallback =
[URL.scheme isEqualToString:@"https"] &&
[URL.host isEqualToString:@"example.com"] &&
[URL.path isEqualToString:@"/oauth/dwolla/callback"];
if (!isCallback) {
return YES;
}
NSURLComponents *components =
[NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO];
NSString *authorizationCode = nil;
NSString *oauthError = nil;
for (NSURLQueryItem *item in components.queryItems) {
if ([item.name isEqualToString:@"code"]) {
authorizationCode = item.value;
} else if ([item.name isEqualToString:@"error"]) {
oauthError = item.value;
}
}
if (oauthError.length > 0) {
NSLog(@"Dwolla authorization failed: %@", oauthError);
return NO;
}
if (authorizationCode.length > 0) {
[self sendAuthorizationCodeToServer:authorizationCode];
return NO;
}
NSLog(@"Dwolla callback did not contain code: %@", URL.absoluteString);
return NO;
}
其中的 example.com 和 /oauth/dwolla/callback 必须替换为实际配置的回调地址。
如果必须继续使用旧 SDK 的 requestAccessToken:,至少要把解析得到的变量传给它:
if (authorizationCode.length > 0) {
[self requestAccessToken:authorizationCode];
return NO;
}
不过,这种方式通常要求客户端持有 Client secret,不适合正式应用。
推荐的授权流程
较为安全的授权流程如下:
- iOS 应用打开 Dwolla 授权页面。
- 用户登录并批准授权。
- Dwolla 跳转到预先注册的
redirect_uri,并附带code。 - iOS 应用截获回调,将
code发送到自己的服务端。 - 服务端使用
Client key、Client secret、code和相同的redirect_uri请求 Dwolla token endpoint。 - 服务端保存 access token,再通过自己的 API 向 iOS 应用提供所需的业务数据。
示意代码如下:
- (void)sendAuthorizationCodeToServer:(NSString *)code
{
NSURL *URL = [NSURL URLWithString:
@"https://api.example.com/oauth/dwolla/exchange"];
NSMutableURLRequest *request =
[NSMutableURLRequest requestWithURL:URL];
request.HTTPMethod = @"POST";
[request setValue:@"application/json"
forHTTPHeaderField:@"Content-Type"];
NSDictionary *body = @{
@"code": code
};
request.HTTPBody =
[NSJSONSerialization dataWithJSONObject:body
options:0
error:nil];
NSURLSessionDataTask *task =
[[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error) {
NSLog(@"Authorization code exchange failed: %@", error);
return;
}
// 根据服务端 API 的响应格式继续处理
}];
[task resume];
}
服务端交换 token 时使用的 redirect_uri 必须与授权请求中的值完全一致,包括协议、域名、路径、端口和尾部斜杠。
使用自定义 URL Scheme 时
原生应用也可以注册自定义回调,例如:
myapp://oauth/dwolla
对应的拦截条件可以改为:
BOOL isCallback =
[URL.scheme isEqualToString:@"myapp"] &&
[URL.host isEqualToString:@"oauth"] &&
[URL.path isEqualToString:@"/dwolla"];
这种回调形式是否可用,取决于当前 Dwolla OAuth 的配置规则。如果 Dwolla 只接受 HTTPS 回调,可以让 HTTPS 页面通过 Universal Link 或受控跳转返回应用。
注意事项
- 不要把
Client secret编译进 iOS 应用。安装包可以被分析,secret 无法真正保密。 - 必须校验 OAuth 的
state参数,防止登录 CSRF 和回调串用。 - 不要记录完整的 authorization code、access token 或 refresh token。
- authorization code 通常只能使用一次,而且有效期较短,应立即发送到服务端进行交换。
UIWebView已被弃用,新项目应使用WKWebView或系统身份验证会话。- 如果
dwolla-ios是旧式 SDK,其中的示例可能对应历史 OAuth 接口。实际授权地址、token endpoint、scope、PKCE 支持情况和移动端限制,应以当前 Dwolla API 文档及应用后台配置为准。 - 如果回调中没有
code,先检查完整回调 URL 中的error、error_description和state,再确认redirect_uri是否完全匹配。
备注:内容仅供参考。
