前面的文章的OAuth认证过程在获取oauth_verifier码是是通过调用android系统带的浏览器进行用户授权认证的, 具体见:
android开发我的新浪微博客户端-用户授权页面功能篇(3.2)。
当初的实现是这样:
1、首先在AndroidManifest.xml中配置给AuthorizeActivity添加如下配置<data android:scheme="myapp" android:host="AuthorizeActivity" /> ,这样在浏览器中通过地址myapp://AuthorizeActivity启动AuthorizeActivity这个Activity.
2、在用android系统带的浏览器显示用户授权页面的时候,把"myapp://AuthorizeActivity"作为url参数CallBackUrl=myapp://AuthorizeActivity,这样当用户输入完账号和密码会请求myapp://AuthorizeActivity?oauth_verifier=123456这个地址,这个时候就自然启动了AuthorizeActivity这个Activity.
3、当AuthorizeActivity被启动的时候,在onNewIntent方法中通过Uri uri = intent.getData();获取oauth_verifier值123456。
这样的实现有个弊端:
就是当用户认证是选择用uc等第三方的浏览器进行用户认证时,当用户输入账号密码后点击授权按钮后不会跳转到AuthorizeActivity这个Activity,只有用android自带的浏览器才没有问题。其实这个是个比较严重的问题了大多数的用户都会用uc等第三方的浏览器了,这样导致认证不能正常进行。
最近在尝试iphone下的oauth认证的时候发现一个很好的思路,那就是不要用系统带的浏览器而且在自己的程序中嵌入一个WebView在这个WebView控件中完成用户授权认证而不再使用额外的浏览器。今天抽了空把在iphone下的实现思路搬到了android下,做了如下的尝试。
现在的实现是这样:
1、首先上面的那3点都可以不需要了,该删除的删除。
2、新建一个名为WebViewActivity的ViewActivity,在这个ViewActivity就只有一个WebView控件
Layout文件如下:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <WebView android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/web"> </WebView> </ScrollView>
3、修改OAuth过程在获取(Request Token和Request Secret)后,启动WebViewActivity,在这个WebViewActivity的WebView控件显示用户认证的网页,代码实现如下:
//
Url类似:
http://api.t.sina.com.cn/oauth/authenticate?oauth_token=71c6df7fd0b4e1e5c491faa90b654fdf
&from=xweibo
String Url
=
getAuthenticationURL(); Intent intent
=
new
Intent(MainActivity.
this
, WebViewActivity.
class
); Bundle b
=
new
Bundle(); b.putString(
"
url
"
, url); intent.putExtras(b); startActivity(intent);
4、在WebViewActivity中onCreate方法中获取上面传过来的url进行认证网页显示,代码如下:
super
.onCreate(savedInstanceState); setContentView(R.layout.webview); WebView wv
=
(WebView) findViewById(R.id.web); Intent i
=
this
.getIntent();
if
(
!
i.equals(
null
)){ Bundle b
=
i.getExtras();
if
(b
!=
null
){
if
(b.containsKey(
"
url
"
)){ String url
=
b.getString(
"
url
"
);
//
这行很重要一点要有,不然网页的认证按钮会无效
wv.getSettings().setJavaScriptEnabled(true);
wv.getSettings().setSupportZoom(
true
);
//
wv.getSettings().setDefaultZoom(WebSettings.ZoomDensity.FAR);
wv.getSettings().setBuiltInZoomControls(
true
);
wv.loadUrl(url); } } }
5、上面Url参数中我们并没有提供CallBackUrl参数,这个时候当用户输入账号和密码点击授权后会显示pin码(oauth_verifier值)页面,而不在会跳转,页面如下:

6、上一步中已经在WebView显示出了授权码,我们接下来要做的就是获取这个授权码176048,这个既然是显示在WebView中的网页,那么我们获取当前的这个WebView显示的html代码然后从中提取出授权码不就可以了。获取html代码是通过调用javascript实现的,不难就解释了看具体代码,在onCreate方法添加如下代码:
wv.addJavascriptInterface(
new
JavaScriptInterface(),
"
Methods
"
); WebViewClient wvc
=
new
WebViewClient() { @Override
public
void
onPageFinished(WebView view,String url) { view.loadUrl(
"
javascript:window.Methods.getHTML('<head>'+document.getElementsByTagName('html')[0].innerHTML+'</head>');
"
);
super
.onPageFinished(view, url); } }; wv.setWebViewClient(wvc);
添加类JavaScriptInterface
class
JavaScriptInterface {
public
void
getHTML(String html) { String pin
=
getPin(html);
//
这里就获取到了我们想要的pin码
//
这个pin码就是oauth_verifier值,用来进一步获取Access Token和Access Secret用
Log.e(
"
pin
"
, pin); } }
7、这样我们就获取到了当前WebView的html代码,接下来就是提取html中的授权码,html代码如下:
<
head
><
head
>
<
meta http
-
equiv
=
"
Content-Type
"
content
=
"
text/html; charset=utf-8
"
>
<
meta name
=
"
viewport
"
content
=
"
width=device-width
"
>
<
title
>
应用授权
-
新浪微博
</
title
>
<
link href
=
"
http://timg.sjs.sinajs.cn/t35/style/css/open/open_third.css
"
rel
=
"
stylesheet
"
type
=
"
text/css
"
>
</
head
><
body
>
<
div
class
=
"
opthBg
"
>
<
div
class
=
"
opthTopBg
"
><
div
class
=
"
opthHead
"
><
a href
=
"
http://weibo.com
"
class
=
"
newlogo
"
></
a
></
div
></
div
>
<
div
class
=
"
opthBox
"
>
<
div
class
=
"
opthCen
"
>
<
br
><
br
>
<
font size
=
"
4
"
><
b
>
获取到授权码:
176048
</
b
></
font
>
<
br
><
br
>
<
div
class
=
"
opthBottom
"
>
<
div
class
=
"
lf
"
></
div
>
<
div
class
=
"
rt
"
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
script type
=
"
text/javascript
"
>
function canchange() { document.forms[
'
authZForm
'
].from.value
=
'
turnuser
'
; document.forms[
'
authZForm
'
].action.value
=
''
; document.forms[
'
authZForm
'
].submit(); }
</
script
>
</
body
></
head
>
提取授权码用正则实现的,java方法代码如下:
public
String getPin(String html) { String ret
=
""
; String regEx
=
"
[0-9]{6}
"
; Pattern p
=
Pattern.compile(regEx); Matcher m
=
p.matcher(html);
boolean
result
=
m.find();
if
(result) { ret
=
m.group(
0
); }
return
ret; }
这样就完成了所有的实现了,这做虽然有点麻烦,但是彻底解决了以前存在的问题。
这代码写的有点瑕疵:返回pin码时,显示授权码的界面会显示几秒钟,而且多数时候我们都不需要去给用户显示这个页面.既然WebViewClient类有onPageFinished() 的函数来执行页面载入完毕后要处理的事情,那肯定也有函数在页面载入之前执行的…翻了翻,的确发现 onPageStarted() 函数,就在 onPageStarted() 里面写点代码,就足以解决问题,而且不需要用到JS去获取该页面中的pin码。
在前面获取Request Token地址的步骤中,callback地址随便给个值,sina 就会给你返回带pin码的URL,分析这个URL把pin码截出来就好了String requestTokenUrl = httpOauthprovider.retrieveRequestToken(httpOauthConsumer, “nic”);然后就是WebViewClient 的 onPageStarted() 函数了:
public void onPageStarted(WebView view, String url, Bitmap favicon) {
int start = url.indexOf(oauth.signpost.OAuth.OAUTH_VERIFIER);
if(start > -1){
String pinStr = url.substring(start);
System.out.println("start----get it! " + pinStr);
intent.putExtra("oauth_verifier", pinStr);
setResult(RESULT_OK, intent);
finish();
}else{
super.onPageStarted(view, url, favicon);
}
}
获取到所要的 oauth_verifier (即pin码)后,直接关闭当前的Activity这样就不会显示授权码的界面了,然后返回上一层Activity即可进行下一步处理。