您的位置:首页 > 移动开发

解读某OAuth 2.0的开源示例android-oauth-app

2016-05-08 18:50 302 查看

解读某OAuth 2.0的开源示例android-oauth-app

OpenContextApps的android-oauth-app是学习OAuth认证时候找到的一个开源代码. 是4年前的一个Android OAuth 2.0 Demo Application. 认证的地址都已经失效了, 但是还是值得一看的.

项目的github地址: https://github.com/OpenConextApps/android-oauth-app

简介:

This mobile application is able to connect to web service secured with OAuth 2.0.

这个app能够使用OAuth 2.0进行web service安全认证.

OAuth Properties 关于OAuth属性的配置文件

The properties file located at res/raw/demo.properties contains the OAuth configuration parameters: 在res/raw/demo.properties中有OAuth的配置参数.

authorize_url=https://frko.surfnetlabs.nl/workshop/php-oauth/authorize.php
authorize_response_type=code
authorize_grant_type=authorization_code
authorize_client_id=oauth-mobile-app
authorize_scope=grades
authorize_redirect_uri=oauth-mobile-app://callback
token_url=https://frko.surfnetlabs.nl/workshop/php-oauth/token.php
token_grant_type=authorization_code
webservice_url=https://frko.surfnetlabs.nl/workshop/php-oauth-grades-rs/api.php/grades/@me


You can modify them for instance to use your own environment. 你可以根据你自己的情况修改它们.

下面开始分析过程:

分析manifest文件

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".StartActivity"
android:label="@string/title_activity_start" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SchemeCaptureActivity"
android:exported="true"
android:label="@string/title_activity_scheme_capture"
android:permission="android.permission.INTERNET" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="oauth-mobile-app" />
</intent-filter>
</activity>
</application>


只有两个activity. 注意到SchemeCaptureActivity中可以单独为Activity设置网络访问权限. 另外注意到intent-filter的设置

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> //指定该Activity能被浏览器安全调用
<data android:scheme="oauth-mobile-app" />
</intent-filter>


这里表示android:scheme指定接受Uri的Scheme为oauth-mobile-app, BROWSABLE的意思就是浏览器在特定条件下可以打开你的activity.

整个流程是StartActivity打开浏览器, 然后浏览器在特定的条件下打开SchemeCaptureActivity.

分析StartActivity类



/**
* The Home Activity to start the application with. The current settings can be
* shown here. With the start button the Authentication flow will be started.
* The scheme of the redirect_url will be catch by The other Activity
* (SchemeCaptureActivity).
* 程序的主Activity. 将显示当前设置. 点击开始按钮将开始认证流程. redirect_url将会被SchemeCaptureActivity捕获.
*
* @author jknoops @ iprofs.nl
*/
public class StartActivity extends Activity {
private AuthenticationDbService service;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v("demo.SActivity", "Starting Demo Application");
setContentView(R.layout.activity_start);
Log.d("demo.SActivity", "Loading properties");
service = AuthenticationDbService.getInstance(this); //加载属性值
Log.d("demo.SActivity", "Initialiing the screen.");
EditText editTextUrl = (EditText) findViewById(R.id.editText_autorize_url);
editTextUrl.setText(service.getAuthorize_url()); //设置认证地址
EditText editTextResponseType = (EditText) findViewById(R.id.editText_response_type);
editTextResponseType.setText(service.getAuthorize_response_type()); //是指响应类型
EditText editTextClientId = (EditText) findViewById(R.id.editText_client_id);
editTextClientId.setText(service.getAuthorize_client_id());//设置认证客户端id
EditText editTextRedirectUri = (EditText) findViewById(R.id.editText_redirect_uri);
editTextRedirectUri.setText(service.getAuthorize_redirect_uri()); //设置重定向uri
Button startButton = (Button) findViewById(R.id.button_start);
startButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//拼接OAuth认证地址
StringBuilder sb = new StringBuilder();
// basic authorize
sb.append(service.getAuthorize_url());
// response type
sb.append("?");
sb.append("response_type=");
sb.append(service.getAuthorize_response_type());
// client_id
sb.append("&");
sb.append("client_id=");
sb.append(service.getAuthorize_client_id());
// scope
sb.append("&");
sb.append("scope=");
sb.append(service.getAuthorize_scope());
// redirect
sb.append("&");
sb.append("redirect_uri=");
sb.append(service.getAuthorize_redirect_uri());
String url = sb.toString();
Log.d("demo.SActivity", "Starting (Starting class) with url = " + url);
//拼接之后的url为https://frko.surfnetlabs.nl/workshop/php-oauth/authorize.php?response_type=code&client_id=oauth-mobile-app&scope=grades&redirect_uri=oauth-mobile-app://callback
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_start, menu);
return true;
}
}


StartActivity从AuthenticationDbService对象中获得了很多数据. 这些数据是从properties中或则从网络请求中获得的数据, 然后保存在SP文件中.

分析AuthenticationDbService类

/**
* A Helper class for storing/retrieving some data. The tokens are stored in a
* Preferences file. The properties are loaded from the properties file.
* 存储/获取一些数据的帮助类. token值被存储在sp文件中. 属性值保存在properties文件中.
*/
public class AuthenticationDbService {
/**
* the local AuthenticationDbService.
* 本地验证数据服务对象
*/
private static AuthenticationDbService _instance;
private static Context _context;
//声明Oauth认证中要使用的参数
private static final String ACCESS_TOKEN = "access_token"; //访问的token
private static final String REFRESH_TOKEN = "refresh_token"; //刷新的token
private static final String TOKEN_TYPE = "token_type"; //token类型
private static final String EXPIRES_IN = "expires_in"; //过期时长
private static final String SCOPE = "scope"; //范围
private static final String EXPIRES_IN_LONG = "expires_in_long"; //过期时间的毫秒值
public static final String RESPONSE_TYPE_TOKEN = "token"; //响应类型token
public static final String RESPONSE_TYPE_CODE = "code"; //响应类型code
/**
* The properties from demo.properties are loaded inside this Properties.
* 从demo.properties中获得属性值
*/
private Properties demoProperties;
/**
* The values for the tokens are stored inside the preferences file.
* 这些token值保存在sp中
*/
private SharedPreferences mPrefs;
/**
* Static getInstance() method for Singleton pattern.
* 单例模式
*
* @return the AuthenticationDbService
*/
public static AuthenticationDbService getInstance(Context context) {
if (_instance == null) {
_context = context;
_instance = new AuthenticationDbService();
}
return _instance;
}
/**
* Private constructor
* 私有化构造方法,从sp中读取配置信息
*/
private AuthenticationDbService() {
mPrefs = _context.getSharedPreferences("OpenConext.demo",
Context.MODE_PRIVATE);
loadProperties(); //加载属性值
}
/**
* Loading properties from the file system.
* 从属性文件中获得属性
*/
private void loadProperties() {
Resources resources = _context.getResources();
// Read from the /res/raw directory
//从 /res/raw目录读取配置信息
try {
//openRawResource()读取raw中文件为流的形式
InputStream rawResource = resources.openRawResource(R.raw.demo);
demoProperties = new Properties();
demoProperties.load(rawResource); //将值到流中
Log.d("demo.DbService", "The properties are now loaded");
Log.v("demo.DbService", "properties: " + demoProperties);
} catch (NotFoundException e) {
Log.e("demo.DbService.error", "Did not find raw resource: " + e);
} catch (IOException e) {
Log.e("demo.DbService.error", "Failed to open demo property file");
}
}
/**
* Retrieving the refresh token from the local storage on the device.
* 获得refresh_token
*/
public String getRefreshToken() {
return mPrefs.getString(REFRESH_TOKEN, null);
}
/**
* Storing the refresh token into the local storage on the device.
* 将refresh_token保存到sp中
*/
public void setRefreshToken(final String token) {
Editor editor = mPrefs.edit();
editor.putString(REFRESH_TOKEN, token);
editor.commit();
}
/**
* Retrieving the access token from the local storage on the device. If
* there is a value "expires in" is stored, there will be checked if the
* access token is still valid. When there is no "expires in" or the access
* token is still valid, the access token will be returned. Otherwise an
* empty access token will be returned.
* <p/>
* 从本地获得access_token. 如果存储了过期时间信息, 那么将要检查access_token的有效性.
* 当没有过期,或者access_token仍然有效, 将会返回access_token. 否则将会返回空值.
*/
public String getAccessToken() {
//获得过期时间
long expires_in_long = mPrefs.getLong(EXPIRES_IN_LONG, -1);
//如果sp不包含expires_in键值, 则直接获得access_token
if (!mPrefs.contains(EXPIRES_IN)) {
return mPrefs.getString(ACCESS_TOKEN, null);
}
//如果没有获得过期时间则access_token为空
if (expires_in_long == -1) {
return "";
}
//获得当前时间的毫秒值
long now_long = new Date().getTime();
//判断过期时间是否大于当前时间
if (expires_in_long > now_long) {
Log.v("access_token", "Expires in " + (expires_in_long - now_long)
+ " milliseconds");
//没有过期则返回access_token
return mPrefs.getString(ACCESS_TOKEN, null);
}
Log.v("access_token", "Overtime " + (now_long - expires_in_long)
+ " milliseconds");
return "";
}
/**
* 设置access_token
*/
public void setAccessToken(final String token) {
Editor editor = mPrefs.edit();
editor.putString(ACCESS_TOKEN, token);
editor.commit();
}
/**
* 获得token_type
*/
public String getTokenType() {
return mPrefs.getString(TOKEN_TYPE, null);
}
/**
* 设置token_type
*/
public void setTokenType(final String type) {
Editor editor = mPrefs.edit();
editor.putString(TOKEN_TYPE, type);
editor.commit();
}
/**
* 获得expires_in过期时长
*/
public Integer getExpiresIn() {
return mPrefs.getInt(EXPIRES_IN, -1);
}
/**
* 设置expires_in过期时长,和expires_in_long过期时间的毫秒值
* 注意:expires_in和expires_in_long总是一起保存的或修改的
*/
public void setExpiresIn(final int expiresIn) {
//获得当期时间的毫秒值
Date nowDate = new Date();
long nowLong = nowDate.getTime();
//计算expires值
long expiresLong = nowLong + (1000l * expiresIn);
//写入sp中
Editor editor = mPrefs.edit();
editor.putLong(EXPIRES_IN_LONG, expiresLong);
editor.putInt(EXPIRES_IN, expiresIn);
editor.commit();
}
/**
* 获取scope
*/
public String getScope() {
return mPrefs.getString(SCOPE, null);
}
/**
* 设置scope
*/
public void setScope(final String scope) {
Editor editor = mPrefs.edit();
editor.putString(SCOPE, scope);
editor.commit();
}

public String getAuthorize_client_secret() {
return demoProperties.getProperty("authorize_client_secret", null);
}
/**
* 获得认证地址
*/
public String getAuthorize_url() {
return demoProperties.getProperty("authorize_url");
}
public String getAuthorize_response_type() {
return demoProperties.getProperty("authorize_response_type");
}
public String getAuthorize_grant_type() {
return demoProperties.getProperty("authorize_grant_type");
}
public String getAuthorize_client_id() {
return demoProperties.getProperty("authorize_client_id");
}
public String getAuthorize_scope() {
return demoProperties.getProperty("authorize_scope");
}
/**
* 获得重定向地址
*/
public String getAuthorize_redirect_uri() {
return demoProperties.getProperty("authorize_redirect_uri");
}
public String getToken_url() {
return demoProperties.getProperty("token_url");
}
public String getToken_grant_type() {
return demoProperties.getProperty("token_grant_type");
}
public String getWebservice_url() {
return demoProperties.getProperty("webservice_url");
}
}


整个本地数据存储服务类AuthenticationDbService都很简单. 我们接下来看看能够被浏览器开启的SchemeCaptureActivity类. 整个类代码很长, 需要一点点耐心.

分析SchemeCaptureActivity类



中间留空的一块为EditText, 用来显示一些错误信息和获得的数据.

/**
* The Scheme Capture Activity will catch the configured scheme of the
* redirect_url. The logic of retrieving the refresh/access token will also be
* done in the class. With a valid access token the configured webservices will
* be requested to get the data.
* <p/>
* SchemeCaptureActivity将捕获redirect_url配置的scheme. 该类会获取refresh_token和access_token.
* 如果webservice请求时携带一个有效的access_token,将能服务器获得数据
*
* @author jknoops @ iprofs.nl
*/
public class SchemeCaptureActivity extends Activity {
Map<String, String> fragments = new HashMap<String, String>(); //使用map保存查询参数
private EditText et;
//响应返回类型是否是code
private boolean isResponseTypeIsCode;
private AuthenticationDbService service = AuthenticationDbService
.getInstance(this);
//获得数据响应类型码的任务
private RetrieveDataResponseTypeCodeTask retrieveDataResponseTypeCodeTask;
//获得refresh_token和access_token的任务
private RetrieveRefreshAndAccessTokenTask retrieveRefreshAndAccessTokenTask;
//获得access_token的任务
private RetrieveAccessTokenTask retrieveAccessTokenTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_schemecapture);
//响应类型是code吗
isResponseTypeIsCode = AuthenticationDbService.RESPONSE_TYPE_CODE
.equals(service.getAuthorize_response_type());
Button refreshButton = (Button) findViewById(R.id.button_refresh);
refreshButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//刷新数据
refreshData();
}
});
//获得传过来的url
Uri data = getIntent().getData();
Log.v("demo.SCActivity", "Uri = " + data.toString());
//响应类型是否为code
if (isResponseTypeIsCode) {
//如果返回的是code,则携带code获得请求参数
retrieveQueryParamatersWithResponseTypeCode(data);
} else {
//如果返回的是token,则携带token获得请求参数
retrieveQueryParametersWithResponseTypeToken(data);
}
et = (EditText) findViewById(R.id.editText_output);
et.setTextSize(10);
refreshData(); //刷新数据
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_start, menu);
return true;
}
/**
* Retrieve the parameters from the response data. This method should only
* be used when the response type is "token". When the response type is
* "token" the response is inside the fragment. The data from the fragment
* will be stored in the local fragments Dictionary.
* <p/>
* 从响应数据中获得参数. 该方法仅仅在响应类型是token的情况下使用.
*
* @param data - the data to split into key/values
*/
private void retrieveQueryParametersWithResponseTypeToken(Uri data) {
//从Uri中获得fragment
String fragment = data.getFragment();
Log.v("demo.SCActivity", "Fragement (Token) = " + fragment);
//按照&切割fragment
String[] pairs = fragment.split("&");
Log.v("demo.SCActivity", "Pairs (Token) = " + pairs.toString());
int i = 0;
String key = "";
String value = "";
StringBuilder sb = new StringBuilder();
//将fragment保存为key-value形式
while (pairs.length > i) {
int j = 0;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == 0) {
key = p;
sb.append(key + " = ");
} else if (j == 1) {
value = p;
fragments.put(key, value);
sb.append(value + "\n");
}
Log.v("demo.SCActivity", "[" + i + "," + j + "] = " + p);
j++;
}
i++;
}
//从fragment中存储token
storeTokenFromFragments();
}
/**
* Retrieve the parameters from the response data. This method should only
* be used when the response type is "code". When the response type is
* "code" the response is in the queryparameters. The data from the
* queryparameters will be stored in the local fragments Dictionary.
* 从响应数据中获得参数. 该方法仅仅被使用在响应类型为code时. 查询参数将会被存储在本地的fragment目录中.
*
* @param data - the data to split into key/values
*/
private void retrieveQueryParamatersWithResponseTypeCode(Uri data) {
//从Uri中获得查询参数
String queryParameters = data.getQuery();
Log.v("demo.SCActivity", "Queryparameters (Code) = " + queryParameters);
//切割查询参数
String[] pairs = queryParameters.split("&");
Log.v("demo.SCActivity", "Pairs (Code) = " + pairs.toString());
int i = 0;
String key = "";
String value = "";
StringBuilder sb = new StringBuilder();
//将查询参数转化为key-value形式
while (pairs.length > i) {
int j = 0;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == 0) {
key = p;
sb.append(key + " = ");
} else if (j == 1) {
value = p;
fragments.put(key, value);
sb.append(value + "\n");
}
Log.v("demo.SCActivity", "[" + i + "," + j + "] = " + p);
j++;
}
i++;
}
}
/**
* A task for retrieving the access and (optional) refresh token. The task
* will be created when needed. The task will be executed in a separate
* Thread. This method should only be used when the response type is "code".
* The authorization code is needed to retrieve the requested tokens. This
* code should be available in the local dictionary fragments.
* 使用code获取refresh_token和access_token. 该方法仅仅被用于响应类型为code时.
* 需要使用refresh_code才能获得authorization code.
*/
private void retrieveRefreshAndAccessTokenWithResponseTypeCode() {
if (retrieveRefreshAndAccessTokenTask == null) {
retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask();
}
Log.d("TASK-1", retrieveRefreshAndAccessTokenTask.toString());
//获取之前的任务状态, 如果为完成,则执行新的异步任务
if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.FINISHED) {
retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask();
Log.d("TASK-1-execute1", "RetrieveRefreshAndAccessTokenTask");
retrieveRefreshAndAccessTokenTask.execute();
} else if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.RUNNING) { //如果还有任务正在进行,则什么都不做,等待上次任务的结束
// log
Log.d("TASK-1-wait", "RetrieveRefreshAndAccessTokenTask");
this.logUI("Please wait...");
} else { //默认为执行新的异步任务
Log.d("TASK-1-execute2", "RetrieveRefreshAndAccessTokenTask");
retrieveRefreshAndAccessTokenTask.execute();
}
}
/**
* A task for retrieving the access token with a current refresh token. The
* task will be created when needed. The task will be executed in a separate
* Thread. This method should only be used when the response type is "code".
* The refresh token should be available in the local DBService
* 该方法逻辑和上面的一样. 使用code获得access_token
*/
private void retrieveAccessTokenWithResponseTypeCode() {
Log.v("demo.OpenConext", "retrieveAccessTokenWithResponseTypeCode");
if (retrieveAccessTokenTask == null) {
retrieveAccessTokenTask = new RetrieveAccessTokenTask();
}
Log.d("TASK-2", retrieveAccessTokenTask.toString());
if (retrieveAccessTokenTask.getStatus() == Status.FINISHED) {
retrieveAccessTokenTask = new RetrieveAccessTokenTask();
Log.d("TASK-2-execute1", "RetrieveAccessTokenTask");
retrieveAccessTokenTask.execute();
} else if (retrieveAccessTokenTask.getStatus() == Status.RUNNING) {
// log
Log.d("TASK-2-wait", "RetrieveAccessTokenTask");
this.logUI("Please wait...");
} else {
Log.d("TASK-2-execute2", "RetrieveAccessTokenTask");
retrieveAccessTokenTask.execute();
}
}
/**
* A task for retrieving the data with a current access token. The task will
* be created when needed. The task will be executed in a separate Thread.
* This method should only be used when the response type is "code". The
* access token should be available in the local DBService
* 该方法逻辑和上面的一样. 使用code,access_token获取数据
*/
private void retrieveDataWithAccessTokenWithResponseTypeCode() {
if (retrieveDataResponseTypeCodeTask == null) {
retrieveDataResponseTypeCodeTask = new RetrieveDataResponseTypeCodeTask();
}
Log.d("TASK-3", retrieveDataResponseTypeCodeTask.toString());
if (retrieveDataResponseTypeCodeTask.getStatus() == Status.FINISHED) {
retrieveDataResponseTypeCodeTask = new RetrieveDataResponseTypeCodeTask();
Log.d("TASK-3-execute1", "RetrieveDataResponseTypeCodeTask");
retrieveDataResponseTypeCodeTask.execute();
} else if (retrieveDataResponseTypeCodeTask.getStatus() == Status.RUNNING) {
// log
Log.d("TASK-3-wait", "RetrieveDataResponseTypeCodeTask");
this.logUI("Please wait...");
} else {
Log.d("TASK-3-execute2", "RetrieveDataResponseTypeCodeTask");
retrieveDataResponseTypeCodeTask.execute();
}
}
//界面显示日志状态
private void logUI(String text) {
Log.v("text-added", text);
if (et.getText() == null) {
et.setText("" + text);
} else {
et.setText("" + et.getText() + "\n" + text);
}
}
/**
* A task for retrieving the data with a current access token. The task will
* be created when needed. The task will be executed in a separate Thread.
* This method should only be used when the response type is "token". The
* access token should be available in the local DBService
* 使用token, access_token获得数据.
* 该方法仅仅被使用在响应类型为token时.
*/
private void retrieveDataWithAccessTokenWithResponseTypeToken() {
//从内存中获得access_token的值
String access_token = fragments.get("access_token");
Log.v("demo.OpenConext", "access_token=" + access_token);
//获得webService地址
String url = service.getWebservice_url();
Log.v("demo.OpenConext", "url=" + url);
BufferedReader in = null;
try {
URL jsonURL = new URL(url);
Log.v("demo.OpenConext", jsonURL.toString());
HttpsURLConnection tc = (HttpsURLConnection) jsonURL
.openConnection();
//https连接,添加请求属性Authorization为access_token
tc.addRequestProperty("Authorization", "Bearer " + access_token);
Log.v("demo.OpenConext", tc.toString());
InputStreamReader isr = new InputStreamReader(tc.getInputStream());
in = new BufferedReader(isr, 256);
//拼接响应数据
StringBuilder sb = new StringBuilder();
sb.append(et.getText());
sb.append("\nLenght=");
sb.append(tc.getContentLength());
sb.append("\nType=");
sb.append(tc.getContentType());
sb.append("\nCode=");
sb.append(tc.getResponseCode());
Log.v("demo.OpenConext", "" + tc.getResponseCode());
sb.append("\nMessage=");
sb.append(tc.getResponseMessage());
//遍历所有的响应头字段
for (String key : tc.getHeaderFields().keySet()) {
Log.v("demo.OpenConext", "key=" + key + " and size="
+ tc.getHeaderField(key).length());
}
//读取输出
String output = "";
if ((output = in.readLine()) != null) {
sb.append("\n");
sb.append(output);
Log.v("demo.OpenConext", "output=" + output);
} else {
sb.append("\n");
sb.append(output);
Log.v("demo.OpenConext", "output=" + output);
}
et.setText(sb.toString());
tc.disconnect();
} catch (Exception e) {
Log.e("demo.OpenConext.error",
"retrieveDataWithAccessTokenWithResponseTypeToken", e);
doAuthentication(); //执行认证
} finally {
try {
in.close();
} catch (IOException e) {
Log.e("demo.OpenConext.error",
"retrieveDataWithAccessTokenWithResponseTypeToken", e);
doAuthentication(); //执行认证
}
}
}
/**
* The default flow for retrieving data. If needed, retrieve refresh and
* access token from the authorization code. If needed, retrieve new access
* token from the refresh token. If needed, retrieve new authorization code.
* retrieve the data.
* 获得数据的默认流程.
* 如果有必要,可以使用authorization code中获得refresh_token和access_token.
* 如果有必要,可以使用refresh_token中获得新的access_token.
* 如果有必要,可以获得新的authorization code
*/
private void refreshData() {
//禁用连接池重用
disableConnectionReuseIfNecessary();
//如果是code类型
if (isResponseTypeIsCode) {
Log.v("demo.OpenConext", "refreshdata: responseType = code");
String accessToken = service.getAccessToken(); //先从本地获得access_token
if (accessToken == null || "".equalsIgnoreCase(accessToken)) { //如果access_token为空
if (service.getRefreshToken() == null || "".equalsIgnoreCase(service.getRefreshToken())) { //如果refresh_token也为空
//使用code获得refresh_token,access_token
retrieveRefreshAndAccessTokenWithResponseTypeCode();
} else { //如果refresh_token不为空,则直接使用code获得access_token
retrieveAccessTokenWithResponseTypeCode();
}
} else { //如果不为空,则直接使用code和access_token获得数据
retrieveDataWithAccessTokenWithResponseTypeCode();
}
} else {
//使用token和access_token获得数据
retrieveDataWithAccessTokenWithResponseTypeToken();
}
}
/**
* For use with android 2.2 (FROYO) the property http.keepAlive needs to be
* set on false. After this the connection pooling won't be used.
* <p/>
* 使用android 2.2(FROYO)系统, http头keepAlive需要设置为false. 设置之后http连接池将不会被使用.
*/
private static void disableConnectionReuseIfNecessary() {
// HTTP connection reuse which was buggy pre-froyo
// 安卓2.2之前http连接重试的的一个bug
if (Build.VERSION.SDK_INT == 8) {
System.setProperty("http.keepAlive", "false");
}
}
/**
* The properties from the tokenString will be stored in the local
* DBService. The access token and the token type are required.
*/
private void storeTokens(String tokenString) throws JSONException {
JSONObject jo = new JSONObject(tokenString);
if ((!jo.has("access_token")) && (!jo.has("token_type"))) {
// Nothing to store
return;
}
service.setAccessToken(jo.getString("access_token"));
service.setTokenType(jo.getString("token_type"));
if (jo.has("refresh_token")) {
service.setRefreshToken(jo.getString("refresh_token"));
}
if (jo.has("expires_in")) {
service.setExpiresIn(jo.getInt("expires_in"));
}
if (jo.has("scope")) {
service.setScope(jo.getString("scope"));
}
}
/**
* The properties from the local fragments will be stored in the DBService.
* 将access_token和token_type保存到本地sp文件
*/
private void storeTokenFromFragments() {
//保存access_token到sp中
if (fragments.containsKey("access_token")) {
service.setAccessToken(fragments.get("access_token"));
} else {
service.setAccessToken("");
}
//保存token_type到sp中
if (fragments.containsKey("token_type")) {
service.setTokenType(fragments.get("toke_type"));
} else {
service.setTokenType("");
}
}
/**
* When the user is required to authenticate again, the refresh and access
* tokens will be removed from the DBService. The right URL will be made
* from the authentication properties. A activity will be activated to
* execute the created URL. Because the of the configured scheme
* configuration, this application will capture the response from the OAuth
* server.
* 当用户再次请求认证,本地存储的refresh_token和access_token将会被删除.
* 将从认证properties中获得正确的URL. 因为配置的scheme信息,一个activity将被激活用来执行创建的url. 该应用将捕捉到OAuth认证服务器的响应.
*/
public void doAuthentication() {
//请求access_token和refresh_token
service.setAccessToken("");
service.setRefreshToken("");
//拼接认证的url
StringBuilder sb = new StringBuilder();
// basic authorize
sb.append(service.getAuthorize_url()); //获得认证地址
// response type
sb.append("?");
sb.append("response_type="); //请求类型
sb.append(service.getAuthorize_response_type());
// client_id
sb.append("&");
sb.append("client_id="); //客户端id
sb.append(service.getAuthorize_client_id());
// scope
sb.append("&");
sb.append("scope="); //范围
sb.append(service.getAuthorize_scope());
// redirect
sb.append("&");
sb.append("redirect_uri="); //重定向地址
sb.append(service.getAuthorize_redirect_uri());
String url = sb.toString();
Log.v("demo.OpenConext", "Starting (Scheme Class) with url = " + url);
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);
}
/**
* A AsyncTask for retrieving the data from the configured webservice with a
* access token. This task should only be used when the response type is
* "code". If needed the access token will be retrieved again. If the
* postExecute the data retrieved from the webservice will be put in a
* component on the screen.
* 使用access_token获得数据.
* 如果有必要access_token将会被重新获得.如果能够获取数据,将显示在屏幕中
*
* @author jknoops @ iprofs.nl
*/
private class RetrieveDataResponseTypeCodeTask extends
AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String result = "";
HttpURLConnection conn = null;
try {
URL webserviceUrl = new URL(service.getWebservice_url());
conn = (HttpURLConnection) webserviceUrl.openConnection();
if (service.getTokenType().equalsIgnoreCase("bearer")) {
conn.setRequestProperty("Authorization", "Bearer "
+ service.getAccessToken());
}
Log.d("demo.OpenConext", conn.getRequestProperties().toString());
Log.v("demo.OpenConext", conn.toString());
InputStreamReader isr = new InputStreamReader(
conn.getInputStream());
BufferedReader in = new BufferedReader(isr, 256);
String response = "";
StringBuilder sb_output = new StringBuilder();
while ((response = in.readLine()) != null) {
Log.v("demo.OpenConext", "response=" + response);
sb_output.append(response);
}
sb_output.append("\n");
result = sb_output.toString();
} catch (MalformedURLException e) {
Log.e("demo.OpenConext.error",
"retrieveDataWithAccessTokenWithResponseTypeCode", e);
} catch (IOException e) {
try {
Log.d("demo.OpenConext.error", "" + conn.getResponseCode()
+ " " + conn.getResponseMessage());
int responseCode = conn.getResponseCode();
if (responseCode == 401) {
// token invalid
// token无效
StringBuilder sb_output = new StringBuilder();
sb_output.append("\n");
sb_output.append("Oops the token is invalid, let me try again!\n");
result = sb_output.toString();
//获取access_token
retrieveAccessTokenWithResponseTypeCode();
} else {
// something else
// 如果是其他错误
StringBuilder sb_output = new StringBuilder();
sb_output.append("\n");
sb_output.append("Oops something happend!\n");
sb_output.append("HTTP response code = " + responseCode
+ "\n");
sb_output.append("HTTP response msg  = "
+ conn.getResponseMessage() + "\n");
result = sb_output.toString();
}
} catch (IOException e1) {
Log.e("demo.OpenConext.error",
"RetrieveDataResponseTypeCodeTask", e);
}
}
return result;
}
@Override
protected void onPostExecute(String result) {
Log.d("DEBUG-RetrieveDataResponseTypeCodeTask", "onPostExecute = "
+ result);
logUI(result);
}
}
/**
* A asynctask for retrieving the access and (optional) refresh token from
* the configured OAuth server. This task should only be used when the
* response type is "code". The authorization code can only be used once.
* After using the authorization code, the code will be removed from the
* local DBService. If the postExecute the tokens will be stored and the
* task for retrieving the data will be executed.
* <p/>
* 从OAuth Server获得refresh_token和access_token的任务.
* 该任务仅仅被用在响应类型为code时.
* authorization code只能被使用一次.
* 使用了authorization code之后, authorization code将从本地移除.
* 在postExecute()中将存储token的值.
* 并执行获得数据的方法
*
* @author jknoops @ iprofs.nl
*/
private class RetrieveRefreshAndAccessTokenTask extends
AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String result = "";
if (fragments.containsKey("code")) {
String code = fragments.get("code");
Log.v("demo.OpenConext", "code=" + code);
//获得token_url
String url = service.getToken_url();
URL tokenUrl;
try {
tokenUrl = new URL(url);
//使用post表单提交
HttpsURLConnection conn = (HttpsURLConnection) tokenUrl
.openConnection();
//将参数进行URL编码
String param = "grant_type="
+ URLEncoder.encode(
service.getAuthorize_grant_type(), "UTF-8")
+ "&code="
+ URLEncoder.encode(code, "UTF-8")
+ "&redirect_uri="
+ URLEncoder.encode(
service.getAuthorize_redirect_uri(),
"UTF-8")
+ "&client_id="
+ URLEncoder.encode(
service.getAuthorize_client_id(), "UTF-8");
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setFixedLengthStreamingMode(param.getBytes().length);
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
// send the POST out
// 发送post数据
PrintWriter out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.close();
// build the string to store the response text from the
// server
String response = "";
Log.d("DEBUG", "" + conn.getResponseCode());
Log.d("DEBUG", "" + conn.getResponseMessage());
// start listening to the stream
Scanner inStream = new Scanner(conn.getInputStream());
// process the stream and store it in StringBuilder
// 处理流,并将数据存入StringBuilder中
while (inStream.hasNextLine()) {
response += (inStream.nextLine());
}
Log.v("demo.OpenConext", response);
result = response;
} catch (MalformedURLException e) {
Log.e("demo.OpenConext.error",
"retrieveRefreshAndAccessTokenWithResponseTypeCode",
e);
} catch (IOException e) {
Log.e("demo.OpenConext.error",
"retrieveRefreshAndAccessTokenWithResponseTypeCode",
e);
}
fragments.remove("code"); //code只能被使用一次
}
return result;
}
@Override
protected void onPostExecute(String result) {
if (result != null && !"".equals(result)) {
try {
storeTokens(result); //存储token
//使用code,access_token获得数据
retrieveDataWithAccessTokenWithResponseTypeCode();
} catch (JSONException e) {
doAuthentication(); //重新执行认证
}
} else {
doAuthentication(); //重写执行认证
}
}
}
/**
* A AsyncTask for retrieving the access token from the configured OAuth
* server with a refresh token. This task should only be used when the
* response type is "code". The refresh token can only be used multiple
* times. If the postExecute the tokens will be stored and the task for
* retrieving the data will be executed.
* 使用refresh_token获得access_token.
* 该任务仅仅被使用在响应类型为code时
* refresh_token可以被使用多次.
* 执行postExecute()时,将保存token值,并执行获得数据的方法.
*
* @author jknoops @ iprofs.nl
*/
private class RetrieveAccessTokenTask extends
AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String result = "";
//获得token_url
String url = service.getToken_url();
URL tokenUrl;
HttpsURLConnection conn = null;
try {
tokenUrl = new URL(url);
conn = (HttpsURLConnection) tokenUrl.openConnection();
//通过refresh_token获得access_token,通过post表单提交
String param = "grant_type="
+ URLEncoder.encode("refresh_token", "UTF-8")
+ "&refresh_token="
+ URLEncoder.encode(service.getRefreshToken(), "UTF-8");
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setFixedLengthStreamingMode(param.getBytes().length);
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
// send the POST out
PrintWriter out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.close();
// build the string to store the response text from the server
String response = "";
// start listening to the stream
Scanner inStream = new Scanner(conn.getInputStream());
// process the stream and store it in StringBuilder
while (inStream.hasNextLine()) {
response += (inStream.nextLine());
}
Log.v("demo.OpenConext", response);
result = response;
} catch (MalformedURLException e) {
Log.e("demo.OpenConext.error",
"retrieveAccessTokenWithResponseTypeCode", e);
} catch (IOException e) {
try {
Log.d("demo.OpenConext.error", "" + conn.getResponseCode()
+ " " + conn.getResponseMessage());
int responseCode = conn.getResponseCode();
if (responseCode == 400) {
Log.v("demo.SCActivity.error",
"Bad request, authenticate again. Refresh token is not valid anymore.");
return "";
} else {
Log.v("demo.SCActivity.error",
"Bad request, authenticate again. Refresh token is not valid anymore.");
// something else
StringBuilder sb_output = new StringBuilder();
sb_output.append("Oops something happend!\n");
sb_output.append("HTTP response code = " + responseCode
+ "\n");
sb_output.append("HTTP response msg  = "
+ conn.getResponseMessage() + "\n");
Log.v("demo.SCActivity.error", sb_output.toString());
return "";
}
} catch (IOException e1) {
Log.e("demo.OpenConext.error",
"RetrieveDataResponseTypeCodeTask", e);
}
}
return result;
}
@Override
protected void onPostExecute(String result) {
if (result != null && !"".equals(result)) {
logUI("Retrieved new Token(s)");
try {
storeTokens(result); //存储token
retrieveDataWithAccessTokenWithResponseTypeCode(); //通过access_token获得数据
} catch (JSONException e) {
doAuthentication(); //重新执行认证
}
} else {
doAuthentication(); //重新执行认证
}
}
}
}


800多行的代码. 其实很多流程都是类似的. 下面来进行分解.

分析onCreate()方法中重要的代码

//响应类型是否为code
if (isResponseTypeIsCode) {
//如果返回的是code,则携带code获得请求参数
retrieveQueryParamatersWithResponseTypeCode(data);
} else {
//如果返回的是token,则携带token获得请求参数
retrieveQueryParametersWithResponseTypeToken(data);
}
//................
refreshData(); //刷新数据


分析retrieveQueryParamatersWithResponseTypeCode()方法

/**
* Retrieve the parameters from the response data. This method should only
* be used when the response type is "code". When the response type is
* "code" the response is in the queryparameters. The data from the
* queryparameters will be stored in the local fragments Dictionary.
* 从响应数据中获得参数. 该方法仅仅被使用在响应类型为code时. 查询参数将会被存储在fragments的map中
*/
private void retrieveQueryParamatersWithResponseTypeCode(Uri data) {
//从Uri中获得查询参数
String queryParameters = data.getQuery();
//切割查询参数
String[] pairs = queryParameters.split("&");
int i = 0;
String key = "";
String value = "";
//将查询参数转化为key-value形式,保存在fragments的map中
StringBuilder sb = new StringBuilder();
while (pairs.length > i) {
int j = 0;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == 0) {
key = p;
sb.append(key + " = ");
} else if (j == 1) {
value = p;
fragments.put(key, value);
sb.append(value + "\n");
}
j++;
}
i++;
}
}


而retrieveQueryParametersWithResponseTypeToken()和retrieveQueryParamatersWithResponseTypeCode()基本相同, 只是最后将获得的token保存到了本地.

分析retrieveQueryParamatersWithResponseTypeToken()方法

/**
* Retrieve the parameters from the response data. This method should only
* be used when the response type is "token". When the response type is
* "token" the response is inside the fragment. The data from the fragment
* will be stored in the local fragments Dictionary.
* <p/>
* 从响应数据中获得参数. 该方法仅仅在响应类型是token的情况下使用.
*/
private void retrieveQueryParametersWithResponseTypeToken(Uri data) {
//从Uri中获得fragment
String fragment = data.getFragment();
//按照&切割fragment
String[] pairs = fragment.split("&");
int i = 0;
String key = "";
String value = "";
StringBuilder sb = new StringBuilder();
//将fragment保存为key-value形式
while (pairs.length > i) {
int j = 0;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == 0) {
key = p;
sb.append(key + " = ");
} else if (j == 1) {
value = p;
fragments.put(key, value);
sb.append(value + "\n");
}
j++;
}
i++;
}
//将fragments中的token保存到本地
storeTokenFromFragments();
}


这里需要说明的是, 这里的fragment不是我们平常使用的那个fragment, 而是Uri的组成部分之一.

Uri的组成部分

[scheme:][//authority][path][?query][#fragment]

详细可以参考这篇文章: Uri详解之——Uri结构与代码提取

看看如何将token相关的信息保存到本地的storeTokenFromFragments()方法

分析storeTokenFromFragments()方法

/**
* The properties from the local fragments will be stored in the DBService.
* 将access_token和token_type保存到本地sp文件
*/
private void storeTokenFromFragments() {
//保存access_token到sp中
if (fragments.containsKey("access_token")) {
service.setAccessToken(fragments.get("access_token"));
} else {
service.setAccessToken("");
}
//保存token_type到sp中
if (fragments.containsKey("token_type")) {
service.setTokenType(fragments.get("toke_type"));
} else {
service.setTokenType("");
}
}


接下来解析refreshData()方法. 这个方法是OAuth认证流程的一个重要的方法.

分析refreshData()方法

/**
* The default flow for retrieving data. If needed, retrieve refresh and
* access token from the authorization code. If needed, retrieve new access
* token from the refresh token. If needed, retrieve new authorization code.
* retrieve the data.
* 获得数据的默认流程.
* 如果有必要,可以使用authorization code中获得refresh_token和access_token.
* 如果有必要,可以使用refresh_token中获得新的access_token.
* 如果有必要,可以获得新的authorization code
*/
private void refreshData() {
//禁用连接池重用
disableConnectionReuseIfNecessary();
//如果是code类型
if (isResponseTypeIsCode) {
String accessToken = service.getAccessToken(); //先从本地获得access_token
if (accessToken == null || "".equalsIgnoreCase(accessToken)) { //如果access_token为空
if (service.getRefreshToken() == null || "".equalsIgnoreCase(service.getRefreshToken())) { //如果refresh_token也为空
//使用code获得refresh_token,access_token
retrieveRefreshAndAccessTokenWithResponseTypeCode();
} else { //如果refresh_token不为空,则直接使用code获得access_token
retrieveAccessTokenWithResponseTypeCode();
}
} else { //如果不为空,则直接使用code和access_token获得数据
retrieveDataWithAccessTokenWithResponseTypeCode();
}
} else {
//使用token和access_token获得数据
retrieveDataWithAccessTokenWithResponseTypeToken();
}
}


首先还是看看 disableConnectionReuseIfNecessary();//禁用连接池重用. 从注释中可以看出android 2.2之前版本的HTTP连接池有一个bug, 这里就是禁用连接池重用.

分析disableConnectionReuseIfNecessary()方法

/**
* For use with android 2.2 (FROYO) the property http.keepAlive needs to be
* set on false. After this the connection pooling won't be used.
* <p/>
* 使用android 2.2(FROYO)系统, http头keepAlive需要设置为false. 设置之后http连接池将不会被使用.
*/
private static void disableConnectionReuseIfNecessary() {
// HTTP connection reuse which was buggy pre-froyo
// 安卓2.2之前http连接重试的的一个bug
if (Build.VERSION.SDK_INT == 8) {
System.setProperty("http.keepAlive", "false");
}
}


其中主要调用的方法有

retrieveRefreshAndAccessTokenWithResponseTypeCode(),

retrieveAccessTokenWithResponseTypeCode(),

retrieveDataWithAccessTokenWithResponseTypeCode(),

这三个方法的代码都很类似. 我们只要看一个就好了.

分析retrieveRefreshAndAccessTokenWithResponseTypeCode()方法

/**
* A task for retrieving the access and (optional) refresh token. The task
* will be created when needed. The task will be executed in a separate
* Thread. This method should only be used when the response type is "code".
* The authorization code is needed to retrieve the requested tokens. This
* code should be available in the local dictionary fragments.
* 使用code获取refresh_token和access_token. 该方法仅仅被用于响应类型为code时.
* 需要使用refresh_code才能获得authorization code.
*/
private void retrieveRefreshAndAccessTokenWithResponseTypeCode() {
if (retrieveRefreshAndAccessTokenTask == null) {
retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask();
}
//获取之前的任务状态, 如果为完成,则执行新的异步任务
if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.FINISHED) {
retrieveRefreshAndAccessTokenTask = new RetrieveRefreshAndAccessTokenTask();
retrieveRefreshAndAccessTokenTask.execute();
} else if (retrieveRefreshAndAccessTokenTask.getStatus() == Status.RUNNING) { //如果还有任务正在进行,则什么都不做,等待上次任务的结束
this.logUI("Please wait...");
} else { //默认为执行新的异步任务
retrieveRefreshAndAccessTokenTask.execute();
}
}


从这些方法可以看出这里使用的是AsyncTask()进行网络请求的. 那么重点也就是这些Task了.

1.RetrieveRefreshAndAccessTokenTask类//如果没有refresh_token和access_token, 则执行RetrieveRefreshAndAccessTokenTask获取.

2.RetrieveAccessTokenTask类 //如果有refresh_token但是没有access_token, 则执行RetrieveAccessTokenTask获取.

3.RetrieveDataResponseTypeCodeTask类 //如果有access_token且响应类型为code, 则执行RetrieveDataResponseTypeCodeTask获取.

4.剩下的一种情况: 如果响应类型为token,则直接调用retrieveDataWithAccessTokenWithResponseTypeToken()获取

分析RetrieveRefreshAndAccessTokenTask类

/**
* A asynctask for retrieving the access and (optional) refresh token from
* the configured OAuth server. This task should only be used when the
* response type is "code". The authorization code can only be used once.
* After using the authorization code, the code will be removed from the
* local DBService. If the postExecute the tokens will be stored and the
* task for retrieving the data will be executed.
* <p/>
* 从OAuth Server获得refresh_token和access_token的任务.
* 该任务仅仅被用在响应类型为code时.
* authorization code只能被使用一次.
* 使用了authorization code之后, authorization code将从本地移除.
* 在postExecute()中将存储token的值.
* 并执行获得数据的方法
*/
private class RetrieveRefreshAndAccessTokenTask extends
AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String result = "";
if (fragments.containsKey("code")) {
String code = fragments.get("code");
//获得token_url
String url = service.getToken_url();
URL tokenUrl;
try {
tokenUrl = new URL(url);
//使用post表单提交
HttpsURLConnection conn = (HttpsURLConnection) tokenUrl
.openConnection();
//将参数进行URL编码
String param = "grant_type="
+ URLEncoder.encode(
service.getAuthorize_grant_type(), "UTF-8")
+ "&code="
+ URLEncoder.encode(code, "UTF-8")
+ "&redirect_uri="
+ URLEncoder.encode(
service.getAuthorize_redirect_uri(),
"UTF-8")
+ "&client_id="
+ URLEncoder.encode(
service.getAuthorize_client_id(), "UTF-8");
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setFixedLengthStreamingMode(param.getBytes().length);
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
// send the POST out
// 发送post数据
PrintWriter out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.close();
// build the string to store the response text from the
// server
String response = "";
Log.d("DEBUG", "" + conn.getResponseCode());
Log.d("DEBUG", "" + conn.getResponseMessage());
// start listening to the stream
Scanner inStream = new Scanner(conn.getInputStream());
// process the stream and store it in StringBuilder
// 处理流,并将数据存入StringBuilder中
while (inStream.hasNextLine()) {
response += (inStream.nextLine());
}
result = response;
} catch (MalformedURLException e) {
Log.e("demo.OpenConext.error",
"retrieveRefreshAndAccessTokenWithResponseTypeCode",
e);
} catch (IOException e) {
Log.e("demo.OpenConext.error",
"retrieveRefreshAndAccessTokenWithResponseTypeCode",
e);
}
fragments.remove("code"); //code只能被使用一次
}
return result;
}
@Override
protected void onPostExecute(String result) {
if (result != null && !"".equals(result)) {
try {
storeTokens(result); //存储token
//使用code,access_token获得数据
retrieveDataWithAccessTokenWithResponseTypeCode();
} catch (JSONException e) {
doAuthentication(); //重新执行认证
}
} else {
doAuthentication(); //重新执行认证
}
}
}


使用POST请求携带code提交. 请求成功后code失效, 将获得的tokens信息保存到本地. 再通过access_token获得数据. 如果没有获得tokens信息, 则重写执行OAuth认证.

保存token信息的方法, 很简单.

分析storeTokens()方法

/**
* The properties from the tokenString will be stored in the local
* DBService. The access token and the token type are required.
* 将token信息保存到本地
*/
private void storeTokens(String tokenString) throws JSONException {
JSONObject jo = new JSONObject(tokenString);
if ((!jo.has("access_token")) && (!jo.has("token_type"))) {
// Nothing to store
return;
}
service.setAccessToken(jo.getString("access_token"));
service.setTokenType(jo.getString("token_type"));
if (jo.has("refresh_token")) {
service.setRefreshToken(jo.getString("refresh_token"));
}
if (jo.has("expires_in")) {
service.setExpiresIn(jo.getInt("expires_in"));
}
if (jo.has("scope")) {
service.setScope(jo.getString("scope"));
}
}


再来看看重新执行认证的方法doAuthentication().

分析doAuthentication()方法

/**
* When the user is required to authenticate again, the refresh and access
* tokens will be removed from the DBService. The right URL will be made
* from the authentication properties. A activity will be activated to
* execute the created URL. Because the of the configured scheme
* configuration, this application will capture the response from the OAuth
* server.
* 当用户再次请求认证,本地存储的refresh_token和access_token将会被删除.
* 将从认证配置文件properties中获得正确的URL. 因为配置的scheme信息,一个activity将被激活用来执行创建的url. 该应用将捕捉到OAuth认证服务器的响应.
*/
public void doAuthentication() {
//请求access_token和refresh_token
service.setAccessToken("");
service.setRefreshToken("");
//拼接认证的url
StringBuilder sb = new StringBuilder();
// basic authorize
sb.append(service.getAuthorize_url()); //获得认证地址
// response type
sb.append("?");
sb.append("response_type="); //请求类型
sb.append(service.getAuthorize_response_type());
// client_id
sb.append("&");
sb.append("client_id="); //客户端id
sb.append(service.getAuthorize_client_id());
// scope
sb.append("&");
sb.append("scope="); //范围
sb.append(service.getAuthorize_scope());
// redirect
sb.append("&");
sb.append("redirect_uri="); //重定向地址
sb.append(service.getAuthorize_redirect_uri());
String url = sb.toString();
Log.v("demo.OpenConext", "Starting (Scheme Class) with url = " + url);
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);
}


这个和StartActivity中的请求验证的代码就是一样的.

分析RetrieveAccessTokenTask类

再来看RetrieveAccessTokenTask, 其实和上面的RetrieveRefreshAndAccessTokenTask流程上没有什么区别. 只是携带的post参数不同而已.

/**
* A AsyncTask for retrieving the access token from the configured OAuth
* server with a refresh token. This task should only be used when the
* response type is "code". The refresh token can only be used multiple
* times. If the postExecute the tokens will be stored and the task for
* retrieving the data will be executed.
* 使用refresh_token获得access_token.
* 该任务仅仅被使用在响应类型为code时
* refresh_token可以被使用多次.
* 执行postExecute()时,将保存token值,并执行获得数据的方法.
*/
private class RetrieveAccessTokenTask extends
AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String result = "";
//获得token_url
String url = service.getToken_url();
URL tokenUrl;
HttpsURLConnection conn = null;
try {
tokenUrl = new URL(url);
conn = (HttpsURLConnection) tokenUrl.openConnection();
//通过refresh_token获得access_token,通过post表单提交
String param = "grant_type="
+ URLEncoder.encode("refresh_token", "UTF-8")
+ "&refresh_token="
+ URLEncoder.encode(service.getRefreshToken(), "UTF-8");
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setFixedLengthStreamingMode(param.getBytes().length);
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
// send the POST out
PrintWriter out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.close();
// build the string to store the response text from the server
String response = "";
// start listening to the stream
Scanner inStream = new Scanner(conn.getInputStream());
// process the stream and store it in StringBuilder
while (inStream.hasNextLine()) {
response += (inStream.nextLine());
}
Log.v("demo.OpenConext", response);
result = response;
} catch (MalformedURLException e) {
Log.e("demo.OpenConext.error",
"retrieveAccessTokenWithResponseTypeCode", e);
} catch (IOException e) {
try {
Log.d("demo.OpenConext.error", "" + conn.getResponseCode()
+ " " + conn.getResponseMessage());
int responseCode = conn.getResponseCode();
if (responseCode == 400) {
Log.v("demo.SCActivity.error",
"Bad request, authenticate again. Refresh token is not valid anymore.");
return "";
} else {
Log.v("demo.SCActivity.error",
"Bad request, authenticate again. Refresh token is not valid anymore.");
// something else
StringBuilder sb_output = new StringBuilder();
sb_output.append("Oops something happend!\n");
sb_output.append("HTTP response code = " + responseCode
+ "\n");
sb_output.append("HTTP response msg  = "
+ conn.getResponseMessage() + "\n");
Log.v("demo.SCActivity.error", sb_output.toString());
return "";
}
} catch (IOException e1) {
Log.e("demo.OpenConext.error",
"RetrieveDataResponseTypeCodeTask", e);
}
}
return result;
}
@Override
protected void onPostExecute(String result) {
if (result != null && !"".equals(result)) {
logUI("Retrieved new Token(s)");
try {
storeTokens(result); //存储token信息到本地
retrieveDataWithAccessTokenWithResponseTypeCode(); //通过access_token获得数据
} catch (JSONException e) {
doAuthentication(); //重新执行认证
}
} else {
doAuthentication(); //重新执行认证
}
}
}


再来看看RetrieveDataResponseTypeCodeTask.

分析RetrieveDataResponseTypeCodeTask类

/**
* A AsyncTask for retrieving the data from the configured webservice with a
* access token. This task should only be used when the response type is
* "code". If needed the access token will be retrieved again. If the
* postExecute the data retrieved from the webservice will be put in a
* component on the screen.
* 使用access_token获得数据.
* 如果有必要access_token将会被重新获得.如果能够获取数据,将显示在屏幕中
*/
private class RetrieveDataResponseTypeCodeTask extends
AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
String result = "";
HttpURLConnection conn = null;
try {
URL webserviceUrl = new URL(service.getWebservice_url());
conn = (HttpURLConnection) webserviceUrl.openConnection();
//携带access_token
if (service.getTokenType().equalsIgnoreCase("bearer")) {
conn.setRequestProperty("Authorization", "Bearer "
+ service.getAccessToken());
}
InputStreamReader isr = new InputStreamReader(conn.getInputStream());
BufferedReader in = new BufferedReader(isr, 256);
String response = "";
StringBuilder sb_output = new StringBuilder();
while ((response = in.readLine()) != null) {
sb_output.append(response);
}
sb_output.append("\n");
result = sb_output.toString();
} catch (MalformedURLException e) {
Log.e("demo.OpenConext.error",
"retrieveDataWithAccessTokenWithResponseTypeCode", e);
} catch (IOException e) {
try {
Log.d("demo.OpenConext.error", "" + conn.getResponseCode()
+ " " + conn.getResponseMessage());
int responseCode = conn.getResponseCode();
if (responseCode == 401) {
// token invalid token无效
StringBuilder sb_output = new StringBuilder();
sb_output.append("\n");
sb_output.append("Oops the token is invalid, let me try again!\n");
result = sb_output.toString();
//使用该access_token不能获得数据的话,那么将重新获取access_token
retrieveAccessTokenWithResponseTypeCode();
} else {
// something else
// 如果是其他错误
StringBuilder sb_output = new StringBuilder();
sb_output.append("\n");
sb_output.append("Oops something happend!\n");
sb_output.append("HTTP response code = " + responseCode
+ "\n");
sb_output.append("HTTP response msg  = "
+ conn.getResponseMessage() + "\n");
result = sb_output.toString();
}
} catch (IOException e1) {
Log.e("demo.OpenConext.error",
"RetrieveDataResponseTypeCodeTask", e);
}
}
return result;
}
@Override
protected void onPostExecute(String result) {
Log.d("DEBUG-RetrieveDataResponseTypeCodeTask", "onPostExecute = "
+ result);
logUI(result);
}
}


最后看看retrieveDataWithAccessTokenWithResponseTypeToken(), 这个方法名长的有点变态了.

分析retrieveDataWithAccessTokenWithResponseTypeToken()方法

/**
* A task for retrieving the data with a current access token. The task will
* be created when needed. The task will be executed in a separate Thread.
* This method should only be used when the response type is "token". The
* access token should be available in the local DBService
* 使用token, access_token获得数据.
* 该方法仅仅被使用在响应类型为token时.
*/
private void retrieveDataWithAccessTokenWithResponseTypeToken() {
//从fragments的map中获得access_token的值
String access_token = fragments.get("access_token");
//获得webService地址
String url = service.getWebservice_url();
BufferedReader in = null;
try {
URL jsonURL = new URL(url);

HttpsURLConnection tc = (HttpsURLConnection) jsonURL
.openConnection();
//https连接,添加请求属性Authorization为access_token
tc.addRequestProperty("Authorization", "Bearer " + access_token);
Log.v("demo.OpenConext", tc.toString());
InputStreamReader isr = new InputStreamReader(tc.getInputStream());
in = new BufferedReader(isr, 256);
//拼接响应数据
StringBuilder sb = new StringBuilder();
sb.append(et.getText());
sb.append("\nLenght=");
sb.append(tc.getContentLength());
sb.append("\nType=");
sb.append(tc.getContentType());
sb.append("\nCode=");
sb.append(tc.getResponseCode());
Log.v("demo.OpenConext", "" + tc.getResponseCode());
sb.append("\nMessage=");
sb.append(tc.getResponseMessage());
//遍历所有的响应头字段
for (String key : tc.getHeaderFields().keySet()) {
Log.v("demo.OpenConext", "key=" + key + " and size="
+ tc.getHeaderField(key).length());
}
//读取输出
String output = "";
if ((output = in.readLine()) != null) {
sb.append("\n");
sb.append(output);
Log.v("demo.OpenConext", "output=" + output);
} else {
sb.append("\n");
sb.append(output);
Log.v("demo.OpenConext", "output=" + output);
}
et.setText(sb.toString());
tc.disconnect();
} catch (Exception e) {
Log.e("demo.OpenConext.error",
"retrieveDataWithAccessTokenWithResponseTypeToken", e);
doAuthentication(); //执行认证
} finally {
try {
in.close();
} catch (IOException e) {
Log.e("demo.OpenConext.error",
"retrieveDataWithAccessTokenWithResponseTypeToken", e);
doAuthentication(); //执行认证
}
}
}


基本上整个流程都已经分析完毕了. 虽然代码有点多, 但是不难, 不是吗?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: