Android中LinkedIn的Oauth 2.0授权
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22062145/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Oauth 2.0 authorization for LinkedIn in Android
提问by amalBit
Even though there is no such android specific sdk from linkedIn(like facebook and twitter sdk for android).Setting up linkedIn authorization with Oauth 1.0 was still easy using:
即使没有来自linkedIn的android特定sdk(如android的facebook和twitter sdk)。使用Oauth 1.0设置linkedIn授权仍然很容易使用:
- scribe-java
- Social-authfor android.
- And the list of toolshere.
But its not the same story for authorization with Oauth2.0. Not too many usefull libraries or android specific examples. I tried using these:
但它与 Oauth2.0 的授权不同。没有太多有用的库或 android 特定示例。我尝试使用这些:
I have read that Oauth 2.0 is much simpler to implement than the 1.0. Still I am not able to do so.
我读过 Oauth 2.0 比 1.0.0 更容易实现。我仍然无法这样做。
Any pointers towards implementing Oauth2.0 for LinkedIn in Android?
在 Android 中为 LinkedIn 实现 Oauth2.0 的任何指示?
回答by amalBit
Oauth2.0 authentication for LinkedIN.
LinkedIN 的 Oauth2.0 身份验证。
Step 1:
第1步:
- Register your app with linkedIn by following this document.And get your api_key and api_secret.
Step 2:
第2步:
MainActivity:
主要活动:
public class MainActivity extends Activity {
/*CONSTANT FOR THE AUTHORIZATION PROCESS*/
/****FILL THIS WITH YOUR INFORMATION*********/
//This is the public api key of our application
private static final String API_KEY = "YOUR_API_KEY";
//This is the private api key of our application
private static final String SECRET_KEY = "YOUR_API_SECRET";
//This is any string we want to use. This will be used for avoiding CSRF attacks. You can generate one here: http://strongpasswordgenerator.com/
private static final String STATE = "E3ZYKC1T6H2yP4z";
//This is the url that LinkedIn Auth process will redirect to. We can put whatever we want that starts with http:// or https:// .
//We use a made up url that we will intercept when redirecting. Avoid Uppercases.
private static final String REDIRECT_URI = "http://com.amalbit.redirecturl";
/*********************************************/
//These are constants used for build the urls
private static final String AUTHORIZATION_URL = "https://www.linkedin.com/uas/oauth2/authorization";
private static final String ACCESS_TOKEN_URL = "https://www.linkedin.com/uas/oauth2/accessToken";
private static final String SECRET_KEY_PARAM = "client_secret";
private static final String RESPONSE_TYPE_PARAM = "response_type";
private static final String GRANT_TYPE_PARAM = "grant_type";
private static final String GRANT_TYPE = "authorization_code";
private static final String RESPONSE_TYPE_VALUE ="code";
private static final String CLIENT_ID_PARAM = "client_id";
private static final String STATE_PARAM = "state";
private static final String REDIRECT_URI_PARAM = "redirect_uri";
/*---------------------------------------*/
private static final String QUESTION_MARK = "?";
private static final String AMPERSAND = "&";
private static final String EQUALS = "=";
private WebView webView;
private ProgressDialog pd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get the webView from the layout
webView = (WebView) findViewById(R.id.main_activity_web_view);
//Request focus for the webview
webView.requestFocus(View.FOCUS_DOWN);
//Show a progress dialog to the user
pd = ProgressDialog.show(this, "", this.getString(R.string.loading),true);
//Set a custom web view client
webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
//This method will be executed each time a page finished loading.
//The only we do is dismiss the progressDialog, in case we are showing any.
if(pd!=null && pd.isShowing()){
pd.dismiss();
}
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String authorizationUrl) {
//This method will be called when the Auth proccess redirect to our RedirectUri.
//We will check the url looking for our RedirectUri.
if(authorizationUrl.startsWith(REDIRECT_URI)){
Log.i("Authorize", "");
Uri uri = Uri.parse(authorizationUrl);
//We take from the url the authorizationToken and the state token. We have to check that the state token returned by the Service is the same we sent.
//If not, that means the request may be a result of CSRF and must be rejected.
String stateToken = uri.getQueryParameter(STATE_PARAM);
if(stateToken==null || !stateToken.equals(STATE)){
Log.e("Authorize", "State token doesn't match");
return true;
}
//If the user doesn't allow authorization to our application, the authorizationToken Will be null.
String authorizationToken = uri.getQueryParameter(RESPONSE_TYPE_VALUE);
if(authorizationToken==null){
Log.i("Authorize", "The user doesn't allow authorization.");
return true;
}
Log.i("Authorize", "Auth token received: "+authorizationToken);
//Generate URL for requesting Access Token
String accessTokenUrl = getAccessTokenUrl(authorizationToken);
//We make the request in a AsyncTask
new PostRequestAsyncTask().execute(accessTokenUrl);
}else{
//Default behaviour
Log.i("Authorize","Redirecting to: "+authorizationUrl);
webView.loadUrl(authorizationUrl);
}
return true;
}
});
//Get the authorization Url
String authUrl = getAuthorizationUrl();
Log.i("Authorize","Loading Auth Url: "+authUrl);
//Load the authorization URL into the webView
webView.loadUrl(authUrl);
}
/**
* Method that generates the url for get the access token from the Service
* @return Url
*/
private static String getAccessTokenUrl(String authorizationToken){
return ACCESS_TOKEN_URL
+QUESTION_MARK
+GRANT_TYPE_PARAM+EQUALS+GRANT_TYPE
+AMPERSAND
+RESPONSE_TYPE_VALUE+EQUALS+authorizationToken
+AMPERSAND
+CLIENT_ID_PARAM+EQUALS+API_KEY
+AMPERSAND
+REDIRECT_URI_PARAM+EQUALS+REDIRECT_URI
+AMPERSAND
+SECRET_KEY_PARAM+EQUALS+SECRET_KEY;
}
/**
* Method that generates the url for get the authorization token from the Service
* @return Url
*/
private static String getAuthorizationUrl(){
return AUTHORIZATION_URL
+QUESTION_MARK+RESPONSE_TYPE_PARAM+EQUALS+RESPONSE_TYPE_VALUE
+AMPERSAND+CLIENT_ID_PARAM+EQUALS+API_KEY
+AMPERSAND+STATE_PARAM+EQUALS+STATE
+AMPERSAND+REDIRECT_URI_PARAM+EQUALS+REDIRECT_URI;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private class PostRequestAsyncTask extends AsyncTask<String, Void, Boolean>{
@Override
protected void onPreExecute(){
pd = ProgressDialog.show(MainActivity.this, "", MainActivity.this.getString(R.string.loading),true);
}
@Override
protected Boolean doInBackground(String... urls) {
if(urls.length>0){
String url = urls[0];
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpost = new HttpPost(url);
try{
HttpResponse response = httpClient.execute(httpost);
if(response!=null){
//If status is OK 200
if(response.getStatusLine().getStatusCode()==200){
String result = EntityUtils.toString(response.getEntity());
//Convert the string result to a JSON Object
JSONObject resultJson = new JSONObject(result);
//Extract data from JSON Response
int expiresIn = resultJson.has("expires_in") ? resultJson.getInt("expires_in") : 0;
String accessToken = resultJson.has("access_token") ? resultJson.getString("access_token") : null;
Log.e("Tokenm", ""+accessToken);
if(expiresIn>0 && accessToken!=null){
Log.i("Authorize", "This is the access Token: "+accessToken+". It will expires in "+expiresIn+" secs");
//Calculate date of expiration
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, expiresIn);
long expireDate = calendar.getTimeInMillis();
////Store both expires in and access token in shared preferences
SharedPreferences preferences = MainActivity.this.getSharedPreferences("user_info", 0);
SharedPreferences.Editor editor = preferences.edit();
editor.putLong("expires", expireDate);
editor.putString("accessToken", accessToken);
editor.commit();
return true;
}
}
}
}catch(IOException e){
Log.e("Authorize","Error Http response "+e.getLocalizedMessage());
}
catch (ParseException e) {
Log.e("Authorize","Error Parsing Http response "+e.getLocalizedMessage());
} catch (JSONException e) {
Log.e("Authorize","Error Parsing Http response "+e.getLocalizedMessage());
}
}
return false;
}
@Override
protected void onPostExecute(Boolean status){
if(pd!=null && pd.isShowing()){
pd.dismiss();
}
if(status){
//If everything went Ok, change to another activity.
Intent startProfileActivity = new Intent(MainActivity.this, ProfileActivity.class);
MainActivity.this.startActivity(startProfileActivity);
}
}
};
}
And the xmlLayout:
和 xmlLayout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<WebView
android:id="@+id/main_activity_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
The token is saved in the sharedpreference file.
令牌保存在 sharedpreference 文件中。
Simple android project repo here in github.
回答by Bhullnatik
I got it working, but it took me... some time.
我让它工作了,但它花了我......一些时间。
I followed LinkedIn Authenticationto manage that.
I still strongly advice to still read this link, as I do not cover all the cases in my examples (errors, error handling, best pratices, parameters usage, precise documentation...)
我按照LinkedIn 身份验证来管理它。
我仍然强烈建议您继续阅读此链接,因为我没有涵盖示例中的所有情况(错误、错误处理、最佳实践、参数使用、精确文档......)
First, you need to have your LinkedIn API Key and Secret Key. If you don't, register an app on here.
Second, you need an Activity in the application that can receive the authorization code. For that, it needs to be set as browsable (launchable from a browser) in the AndroidManifest.xml file :
<activity android:name=".ResultActivity" android:label="" > <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> </intent-filter>
Although not recommended, it's possible to use a data tag to retrieve URIs using a custom scheme :
<data android:scheme="oauth"/>
After that, you need to redirect the user to the LinkedIn's authorization dialog, using a specific URL :
https://www.linkedin.com/uas/oauth2/authorization?response_type=code &client_id=YOUR_API_KEY &scope=SCOPE &state=STATE &redirect_uri=YOUR_REDIRECT_URI
You can use a WebView to directly show it in your application, or let the system handle it through an Intent like :
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(/* FULL URL */)); startActivity(intent);
The only problem here is that the API does not accept schemes other than http or https, meaning you can't just pass the intent URI as the redirect_uri parameter.
So I created a landing page on my server, with only purpose is to redirect to the application. We can imagine something like (in ugly shorten PHP) (Intent ref.):
header('Location: ' . "intent:#Intent;component=your.package/.ResultActivity;S.code=" . $_GET['code'] . ";S.state=" . $_GET['state'] . ";end"); die();
So everything's set! Now the
onCreate(Bundle)
of the ResultActivity :Intent intent = getIntent(); String authorizationCode = intent.getStringExtra("code");
There is another way to pass parameters here, if the data tag was used earlier.
Almost there! Now you just need to perform a simple POST request on that URL :
https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code &code=AUTHORIZATION_CODE &redirect_uri=YOUR_REDIRECT_URI &client_id=YOUR_API_KEY &client_secret=YOUR_SECRET_KEY
Returning a JSONobject on success :
{"expires_in":5184000,"access_token":"AQXdSP_W41_UPs5ioT_t8HESyODB4FqbkJ8LrV_5mff4gPODzOYR"}
其次,在应用中需要一个可以接收授权码的Activity。为此,它需要在 AndroidManifest.xml 文件中设置为可浏览(可从浏览器启动):
<activity android:name=".ResultActivity" android:label="" > <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> </intent-filter>
虽然不推荐,但可以使用数据标签来使用自定义方案检索 URI:
<data android:scheme="oauth"/>
之后,您需要使用特定 URL 将用户重定向到 LinkedIn 的授权对话框:
https://www.linkedin.com/uas/oauth2/authorization?response_type=code &client_id=YOUR_API_KEY &scope=SCOPE &state=STATE &redirect_uri=YOUR_REDIRECT_URI
您可以使用 WebView 直接在您的应用程序中显示它,或者让系统通过 Intent 来处理它,例如:
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(/* FULL URL */)); startActivity(intent);
这里唯一的问题是 API 不接受 http 或 https 以外的方案,这意味着您不能只将意图 URI 作为 redirect_uri 参数传递。
所以我在我的服务器上创建了一个登陆页面,唯一的目的是重定向到应用程序。我们可以想象这样的事情(在丑陋的缩短 PHP)(意图引用。):
header('Location: ' . "intent:#Intent;component=your.package/.ResultActivity;S.code=" . $_GET['code'] . ";S.state=" . $_GET['state'] . ";end"); die();
所以一切都准备好了!现在
onCreate(Bundle)
的 ResultActivity :Intent intent = getIntent(); String authorizationCode = intent.getStringExtra("code");
如果之前使用过数据标签,这里还有另一种传递参数的方法。
差不多好了!现在您只需要对该 URL 执行一个简单的 POST 请求:
https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code &code=AUTHORIZATION_CODE &redirect_uri=YOUR_REDIRECT_URI &client_id=YOUR_API_KEY &client_secret=YOUR_SECRET_KEY
成功返回JSON对象:
{"expires_in":5184000,"access_token":"AQXdSP_W41_UPs5ioT_t8HESyODB4FqbkJ8LrV_5mff4gPODzOYR"}
Et voilà !You can now make your API calls using the access_token. Don't forget to store itsomewhere so you don't have through these steps again.
等等!您现在可以使用 access_token 进行 API 调用。不要忘记将它存储在某个地方,这样您就不会再次执行这些步骤。
I hope this wasn't too long to read and that it can help some people. :)
我希望这不会太长,阅读它可以帮助一些人。:)
回答by huy.nguyen
OAuth 2.0 is much simpler than 1.0 and can be done without any help from an external library. However, if you are already using scribe-java, it will be even easier.
OAuth 2.0 比 1.0 简单得多,无需外部库的任何帮助即可完成。但是,如果您已经在使用 scribe-java,那就更容易了。
The implementation is straight-forward. You need to create a WebView
that has a custom WebViewClient
which captures and overrides loading behavior for your callback URL. Thus, when the WebView
attempts to load that URL, you can intercept the process and extract a verifier. The verifier can be passed to scribe-java to exchange for an access token.
实施是直截了当的。您需要创建一个WebView
具有自定义功能的自定义对象WebViewClient
,用于捕获和覆盖回调 URL 的加载行为。因此,当WebView
尝试加载该 URL 时,您可以拦截该过程并提取验证器。可以将验证器传递给 scribe-java 以交换访问令牌。
To start the whole process, you just need to tell your WebView
to load the authorization URL.
要开始整个过程,您只需要告诉您WebView
加载授权 URL。
I have sample code hosted here. The app authenticates with Buffer's API but most of the code can be reused. You may be interested in the fragmentwhich hosts my custom WebView
and the background jobthat gets access token.
我在这里托管了示例代码。该应用程序使用 Buffer 的 API 进行身份验证,但大部分代码可以重用。您可能对托管我的自定义和获取访问令牌的后台作业的片段感兴趣。WebView
Feel free to ask me any follow up questions.
随时问我任何后续问题。
回答by vntstudy
@Override
public boolean shouldOverrideUrlLoading(WebView view, String authorizationUrl) {
//This method will be called when the Auth proccess redirect to our RedirectUri.
//We will check the url looking for our RedirectUri.
if(authorizationUrl.startsWith(REDIRECT_URI)){
Log.i("Authorize", "");
Uri uri = Uri.parse(authorizationUrl);
//We take from the url the authorizationToken and the state token. We have to check that the state token returned by the Service is the same we sent.
//If not, that means the request may be a result of CSRF and must be rejected.
String stateToken = uri.getQueryParameter(STATE_PARAM);
if(stateToken==null || !stateToken.equals(STATE)){
Log.e("Authorize", "State token doesn't match");
return true;
}
//If the user doesn't allow authorization to our application, the authorizationToken Will be null.
String authorizationToken = uri.getQueryParameter(RESPONSE_TYPE_VALUE);
if(authorizationToken==null){
Log.i("Authorize", "The user doesn't allow authorization.");
return true;
}
Log.i("Authorize", "Auth token received: "+authorizationToken);
//Generate URL for requesting Access Token
String accessTokenUrl = getAccessTokenUrl(authorizationToken);
//We make the request in a AsyncTask
new PostRequestAsyncTask().execute(accessTokenUrl);
}else{
//Default behaviour
Log.i("Authorize","Redirecting to: "+authorizationUrl);
webView.loadUrl(authorizationUrl);
}
return true;
}
And in your AsyncTask:
在你的 AsyncTask 中:
private class PostRequestAsyncTask extends AsyncTask<String, Void, Boolean>{
@Override
protected void onPreExecute(){
pd = ProgressDialog.show(MainActivity.this, "", MainActivity.this.getString(R.string.loading),true);
}
@Override
protected Boolean doInBackground(String... urls) {
if(urls.length>0){
String url = urls[0];
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpost = new HttpPost(url);
try{
HttpResponse response = httpClient.execute(httpost);
if(response!=null){
//If status is OK 200
if(response.getStatusLine().getStatusCode()==200){
String result = EntityUtils.toString(response.getEntity());
//Convert the string result to a JSON Object
JSONObject resultJson = new JSONObject(result);
//Extract data from JSON Response
int expiresIn = resultJson.has("expires_in") ? resultJson.getInt("expires_in") : 0;
String accessToken = resultJson.has("access_token") ? resultJson.getString("access_token") : null;
Log.e("Tokenm", ""+accessToken);
if(expiresIn>0 && accessToken!=null){
Log.i("Authorize", "This is the access Token: "+accessToken+". It will expires in "+expiresIn+" secs");
//Calculate date of expiration
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, expiresIn);
long expireDate = calendar.getTimeInMillis();
////Store both expires in and access token in shared preferences
SharedPreferences preferences = MainActivity.this.getSharedPreferences("user_info", 0);
SharedPreferences.Editor editor = preferences.edit();
editor.putLong("expires", expireDate);
editor.putString("accessToken", accessToken);
editor.commit();
return true;
}
}
}
}catch(IOException e){
Log.e("Authorize","Error Http response "+e.getLocalizedMessage());
}
catch (ParseException e) {
Log.e("Authorize","Error Parsing Http response "+e.getLocalizedMessage());
} catch (JSONException e) {
Log.e("Authorize","Error Parsing Http response "+e.getLocalizedMessage());
}
}
return false;
}
@Override
protected void onPostExecute(Boolean status){
if(pd!=null && pd.isShowing()){
pd.dismiss();
}
if(status){
//If everything went Ok, change to another activity.
Intent startProfileActivity = new Intent(MainActivity.this, ProfileActivity.class);
MainActivity.this.startActivity(startProfileActivity);
}
}
};