解读某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 demoPropertie