Android中LinkedIn的Oauth 2.0授权

Posted

技术标签:

【中文标题】Android中LinkedIn的Oauth 2.0授权【英文标题】:Oauth 2.0 authorization for LinkedIn in Android 【发布时间】:2014-03-30 12:09:07 【问题描述】:

即使没有来自linkedIn 的此类特定于android 的sdk(如android 的facebook 和twitter sdk)。使用Oauth 1.0 设置linkedIn 授权仍然很容易使用:

scribe-java Social-auth 安卓版。 还有tools 的列表。

但使用 Oauth2.0 进行授权的情况并非如此。没有太多有用的库或 android 特定示例。我尝试使用这些:

Android Oauth-client tnj。

我读到 Oauth 2.0 比 1.0 更容易实现。我仍然无法这样做。

任何关于在 Android 中为 LinkedIn 实现 Oauth2.0 的指针?

【问题讨论】:

【参考方案1】:
            @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;
        

在你的 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);
        
    

;

【讨论】:

【参考方案2】:

LinkedIN 的 Oauth2.0 身份验证。

第 1 步:

通过关注this document. 向linkedIn 注册您的应用并获取您的api_key 和api_secret。

第 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);
        
    

;

还有 xmlLayout:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_
    android:layout_
    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_
        android:layout_ />

</LinearLayout>

令牌保存在 sharedpreference 文件中。

简单的 android 项目 repo here in github.

【讨论】:

重定向 URL 需要相同吗?我可以给一些其他的网址吗? 我有一个奇怪的问题,弹出后显示 android 键盘出现但在单击字母时不起作用。什么?我无法输入登录信息。 只想提一下,您不能再使用随机的redirect_uri:您必须使用您在填写的“添加应用”表单中指定的那个。 如果有人想进一步了解 OAuth 和 STATE 常量:twobotechnologies.com/blog/2014/02/… 完美运行。感谢分享【参考方案3】:

OAuth 2.0 比 1.0 简单得多,无需任何外部库的帮助即可完成。但是,如果您已经在使用 scribe-java,那就更简单了。

实现很简单。您需要创建一个具有自定义 WebViewClientWebView,它会捕获并覆盖回调 URL 的加载行为。因此,当WebView 尝试加载该 URL 时,您可以拦截该过程并提取验证程序。可以将验证者传递给 scribe-java 以交换访问令牌。

要开始整个过程​​,您只需要告诉您的WebView 加载授权URL。

我有托管here 的示例代码。该应用程序使用 Buffer 的 API 进行身份验证,但大部分代码都可以重用。您可能对托管我的自定义WebView 的fragment 和获取访问令牌的背景job 感兴趣。

如有任何后续问题,请随时问我。

【讨论】:

嘿,我检查了你的代码,但无法理解 oauth 机制。能否对代码中验证器的提取给出更好的解释? 你的意思是line?回调看起来像这样:callback_url?code="verifier"(即:nguyenhuy.me/callback?code="verifier")。要提取验证器,您只需要通过调用Uri.getQueryParameter(String) 来获取“代码”的值。 您的意思是 LinkedIn OAuth 的工作示例?我可以做到,但代码看起来和我提到的代码真的很相似。【参考方案4】:

我得到了它的工作,但它花了我一些时间。

我关注 LinkedIn Authentication 来管理它。 我仍然强烈建议您仍然阅读此链接,因为我没有涵盖示例中的所有情况(错误、错误处理、最佳实践、参数使用、精确文档......)

首先,您需要拥有 LinkedIn API 密钥和密钥。如果没有,请在 here.

上注册一个应用程序

其次,你需要在应用程序中有一个可以接收授权码的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)(Intent ref.):

header('Location: ' . "intent:#Intent;component=your.package/.ResultActivity;S.code=" . $_GET['code'] . ";S.state=" . $_GET['state'] . ";end");
die();

一切就绪!现在是 ResultActivity 的onCreate(Bundle)

Intent intent = getIntent();
String authorizationCode = intent.getStringExtra("code");

这里还有另外一种传参方式,如果之前使用了data标签的话。

快到了!现在您只需要对该 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"

等等!您现在可以使用 access_token 进行 API 调用。不要忘记在某个地方store it,这样您就不必再经历这些步骤了。

我希望这不是太长的阅读,它可以帮助一些人。 :)

【讨论】:

php中可以不用登陆页吗? @amalBit 有一些方法可以解决它,但这是修改 Oauth2 自然恕我直言。至于解决方案,您可以使用 WebView 和@Override shouldOverrideUrlLoading,如@huy.nguyen 的示例所示,甚至可以使用http://localhost 作为redirect_uri 并监听正在进行的连接。

以上是关于Android中LinkedIn的Oauth 2.0授权的主要内容,如果未能解决你的问题,请参考以下文章

无法添加 OAuth 2.0 范围 LinkedIn

LinkedIn OAuth2 /oauth/v2/accessToken 响应未返回“token_type”

Linkedin OAuth 2.0 重定向 URL 不能包含片段标识符 (#)

LinkedIn + 电子邮件的这个 Oauth 流程有啥问题?

Linkedin Oauth Javascript 授权“哦哦!”

尽管按照指南中的链接传递了正确的信息,但在linkedin OAuth 集成中出现错误