解讀某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, -);
//如果sp不包含expires_in鍵值, 則直接獲得access_token
if (!mPrefs.contains(EXPIRES_IN)) {
return mPrefs.getString(ACCESS_TOKEN, null);
}
//如果沒有獲得過期時間則access_token為空
if (expires_in_long == -) {
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, -);
}
/**
* 設定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 + (l * 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();
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 = ;
String key = "";
String value = "";
StringBuilder sb = new StringBuilder();
//将fragment儲存為key-value形式
while (pairs.length > i) {
int j = ;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == ) {
key = p;
sb.append(key + " = ");
} else if (j == ) {
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 = ;
String key = "";
String value = "";
StringBuilder sb = new StringBuilder();
//将查詢參數轉化為key-value形式
while (pairs.length > i) {
int j = ;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == ) {
key = p;
sb.append(key + " = ");
} else if (j == ) {
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, );
//拼接響應資料
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 == ) {
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, );
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 == ) {
// 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 == ) {
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 = ;
String key = "";
String value = "";
//将查詢參數轉化為key-value形式,儲存在fragments的map中
StringBuilder sb = new StringBuilder();
while (pairs.length > i) {
int j = ;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == ) {
key = p;
sb.append(key + " = ");
} else if (j == ) {
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 = ;
String key = "";
String value = "";
StringBuilder sb = new StringBuilder();
//将fragment儲存為key-value形式
while (pairs.length > i) {
int j = ;
String[] part = pairs[i].split("=");
while (part.length > j) {
String p = part[j];
if (j == ) {
key = p;
sb.append(key + " = ");
} else if (j == ) {
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 == ) {
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 == ) {
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, );
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 == ) {
// 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, );
//拼接響應資料
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(); //執行認證
}
}
}
- 基本上整個流程都已經分析完畢了. 雖然代碼有點多, 但是不難, 不是嗎?