简体   繁体   English

使用Google+的Soundcloud API移动OAUTH登录无法在Android上运行

[英]Soundcloud API mobile OAUTH login using Google+ isn't working on Android

I am working on an Android application that uses the mobile SoundCloud web auth page to log in to SoundCloud. 我正在开发一个使用移动SoundCloud web auth页面登录SoundCloud的Android应用程序。 The SoundCloud mobile web auth page provides you with three options for logging in, using SoundCloud, Facebook, or Google+. SoundCloud移动网络身份验证页面使用SoundCloud,Facebook或Google+为您提供三种登录选项。 The interface looks like so: 界面如下所示:

soundcloud登录

So far I can log in using my SoundCloud and my Facebook credentials but I fail when using Google+. 到目前为止,我可以使用我的SoundCloud和我的Facebook凭据登录,但是在使用Google+时我失败了。 Here's an abridged version of what I am doing: 这是我正在做的简略版本:

public class SoundCloudActivity extends Activity {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.twitter_login_layout);
        ...

        loadingProgressBar = (ProgressBar) findViewById(R.id.loading_progressbar);
        WebView webView = (WebView) findViewById(R.id.login_webview);
        webView.setVerticalScrollBarEnabled(true);
        webView.setHorizontalScrollBarEnabled(true);
        webView.setWebViewClient(new SoundcloudWebViewClient());
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setAllowFileAccess(true);
        webView.getSettings().setPluginState(PluginState.ON);
        webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        authUrl = Api.wrapper.authorizationCodeUrl(Endpoints.CONNECT, Token.SCOPE_NON_EXPIRING).toString();

        webView.loadUrl(authUrl);
    }


    private class SoundcloudWebViewClient extends WebViewClient {

        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(TAG, "shouldOverrideUrlLoading(): url: "+url);
            if (url.startsWith(REDIRECT_URI.toString())) {
                Uri result = Uri.parse(url);
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            token = Api.wrapper.authorizationCode(code, Token.SCOPE_NON_EXPIRING);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        ...
                    }
                }).start();
                return true;
            } else if (url.startsWith("authorize")) {
                return false;
            } else if (url.startsWith("http")) {
                view.loadUrl(url);
            }
            return true;
        }

        @Override
        public void onReceivedError(WebView view, int errorCode,
                String description, String failingUrl) {
            Log.d(TAG, "Call onError with error: "+description);
            super.onReceivedError(view, errorCode, description, failingUrl);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            Log.d(TAG,"onPageStarted(): url: "+url+" favicon: "+favicon);
            loadingProgressBar.setVisibility(ProgressBar.VISIBLE);
            super.onPageStarted(view, url, favicon);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            loadingProgressBar.setVisibility(ProgressBar.GONE);
            super.onPageFinished(view, url);
        }
    }
}

When choosing to use Google+, it redirects me to the familiar Google login page. 选择使用Google+时,会将我重定向到熟悉的Google登录页面。 Then when I enter my username and password, it redirects me to a blank page and does nothing including not providing me with an auth token. 然后,当我输入我的用户名和密码时,它会将我重定向到一个空白页面并且不执行任何操作,包括不向我提供身份验证令牌。 This is a sample URL of the blank page that is generated after logging in: 这是登录后生成的空白页的示例URL:

https://accounts.google.com/o/oauth2/auth?client_id=984739005367.apps.googleusercontent.com&redirect_uri=postmessage&response_type=code%20token%20id_token%20gsession&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.login%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&state=539399140%7C0.873620491&access_type=offline&request_visible_actions=http%3A%2F%2Fschemas.google.com%2FAddActivity%20http%3A%2F%2Fschemas.google.com%2FListenActivity%20http%3A%2F%2Fschemas.google.com%2FCreateActivity&after_redirect=keep_open&cookie_policy=single_host_origin&include_granted_scopes=true&proxy=oauth2relay763648117&origin=https%3A%2F%2Fsoundcloud.com& https://accounts.google.com/o/oauth2/auth?client_id=984739005367.apps.googleusercontent.com&redirect_uri=postmessage&response_type=code%20token%20id_token%20gsession&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth% 2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.login%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&状态= 539399140%7C0.873620491&ACCESS_TYPE =离线&request_visible_actions = HTTP% 3A%2F%2Fschemas.google.com%2FAddActivity%20http%3A%2F%2Fschemas.google.com%2FListenActivity%20http%3A%2F%2Fschemas.google.com%2FCreateActivity&after_redirect = keep_open&cookie_policy = single_host_origin&include_granted_scopes =真代理= oauth2relay763648117&原点= HTTPS% 3A%2F%2Fsoundcloud.com&

I'm wondering if there is a setting that I'm missing in WebView . 我想知道WebView中是否存在我缺少的设置。 I've already had to enable others to get other features in the SoundCloud mobile web page working. 我已经不得不让其他人在SoundCloud移动网页上获得其他功能。 Any suggestions would be very much appreciated. 任何建议将非常感谢。

So Google+ uses cross-site javascript injection to complete the authentication process which requires that the SoundCloud login window still be open during the Google auth process. 因此,Google+使用跨站点JavaScript注入来完成身份验证过程,这需要在Google身份验证过程中仍然打开SoundCloud登录窗口。 To handle this you need to force/allow the Google auth into a new webview window. 要处理此问题,您需要强制/允许Google身份验证进入新的Web视图窗口。 I created a demo project on github that shows the whole process here . 我创建了一个GitHub的示范项目,显示整个过程在这里

This is the class that does the work, look at comments throughout for more details: 这是完成工作的类,查看整个注释以获取更多详细信息:

package com.bulwinkel.soundcloudlogin;

import android.content.Context;
import android.content.DialogInterface; 
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;

public class SoundCloudLoginActivity extends AppCompatActivity {

  private static final String TAG = SoundCloudLoginActivity.class.getSimpleName();

  //todo - create a project in the SoundCloud developer portal: https://soundcloud.com/you/apps/
  private static final String CALLBACK_SCHEME = "soundcloudlogindemo://authentication.complete"; //todo - replace
  private static final String CLIENT_ID = "e64276127b07b38ddfaf1ee458ffc2ac"; //todo - replace
  private static final String STATE = SoundCloudLoginActivity.class.getCanonicalName();

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // clear the cookies to make sure the that the user is properly logged out
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
      final CookieManager cookieManager = CookieManager.getInstance();
      cookieManager.removeAllCookies(null);
      cookieManager.flush();
    } else {
      CookieSyncManager.createInstance(getApplicationContext()).startSync();
      final CookieManager cookieManager = CookieManager.getInstance();
      cookieManager.removeAllCookie();
      cookieManager.removeSessionCookie();
    }

    // SoundCloud oauth url
    final Uri authUri = new Uri.Builder().scheme("https")
        .authority("soundcloud.com")
        .appendPath("connect")
        .appendQueryParameter("scope", "non-expiring")
        .appendQueryParameter("response_type", "code")
        .appendQueryParameter("state", STATE)
        .appendQueryParameter("display", "popup")
        .appendQueryParameter("client_id", CLIENT_ID)
        .appendQueryParameter("redirect_uri", CALLBACK_SCHEME)
        .build();

    Log.d(TAG, "https://soundcloud.com/connect?scope=non-expiring&response_type=code&state=boxset.SoundCloudLoginActivity&display=popup&client_id=6d483c5c02062da985379c36b5e7da95&redirect_uri=http%3A%2F%2Fwonder.fm%2Fincoming%2Fsoundcloud%2Fauth%2F");
    Log.d(TAG, authUri.toString());

    // we need a handle to this to add the second webview during google plus login
    final FrameLayout container = (FrameLayout) findViewById(R.id.container);

    // progress hud adds itself to the view hierarchy
    final LoadingHud loadingHud = new LoadingHud(container);

    final WebView webView = createWebView(this);
    webView.loadUrl(authUri.toString());

    final WebViewClient webViewClient = new WebViewClient() {

      // need to use the depricated method if you are supporting less than api 21
      @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {

        //GUARD - been stung by this
        if (url == null) return false;

        //GUARD - check if we have got our callback url yet
        // this occurs when navigating to facebook and google plus login screens
        if (!url.contains(CALLBACK_SCHEME)) return false;

        final Uri uri = Uri.parse(url);

        //GUARD
        // the state query parameter is echoed back to us so we
        // know that the code is coming from a legitimate source
        final String state = uri.getQueryParameter("state");
        if (!STATE.equals(state)) return false;

        //GUARD
        final String code = uri.getQueryParameter("code");
        if (code == null) {
          // something went wrong during the auth process
          // you need to handle this
          Log.d(TAG, "No code returned from auth process");
          return false;
        }

        // you now have you code to use in the next step of the oauth process
        Log.i(TAG, "code = " + code);
        new AlertDialog.Builder(view.getContext())
            .setTitle("Auth Successful")
            .setMessage("Code: " + code)
            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
              @Override public void onClick(DialogInterface dialogInterface, int i) {
                finish();
              }
            })
            .create()
            .show();

        return true;
      }

      @Override public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        loadingHud.show();
      }

      @Override public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        loadingHud.hide(true);
      }
    };
    webView.setWebViewClient(webViewClient);

    // require for google login
    // google login requires that the SoundCloud login window be open at the same time
    // as it uses cross window/site javascript injection to pass information back to
    // SoundCloud on completion
    webView.setWebChromeClient(new WebChromeClient() {
      @Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture,
          Message resultMsg) {

        // this WebView has to has the same settings as the original for
        // the cross site javascript injection to work
        final WebView googleSignInWebView = createWebView(view.getContext());
        googleSignInWebView.setWebChromeClient(this);
        googleSignInWebView.setWebViewClient(webViewClient);

        container.addView(googleSignInWebView);

        // this is the glue code that wires the original webview
        // and the new webview together so they can communicate
        final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
        transport.setWebView(googleSignInWebView);
        resultMsg.sendToTarget();

        // this advises that we have actually created and displayed the new window
        return true;
      }

      // since we added the window we also have to handle removing it
      @Override public void onCloseWindow(WebView window) {
        container.removeView(window);
      }
    });

    container.addView(webView);

  }



  /**
   * @param context  the WebView must be given an activity context (instead of application context)
   *                 or it will crash in versions less than 4.4
   *
   * @return a {@link WebView} suitable for the soundcloud login process
   */
  private static WebView createWebView(Context context) {
    final WebView webView = new WebView(context);

    final WebSettings settings = webView.getSettings();

    // this allows the username and password validation to work
    settings.setJavaScriptEnabled(true);

    // these 2 are for login with google support
    // which needs to open a second window
    settings.setJavaScriptCanOpenWindowsAutomatically(true);
    settings.setSupportMultipleWindows(true);

    // prevent caching of user data
    settings.setSaveFormData(false);

    // prevents the webview asking the user if they want to save their password
    // needed for pre 18 devices
    settings.setSavePassword(false);

    return webView;
  }

  private static class LoadingHud {

    private final RelativeLayout container;

    public LoadingHud(ViewGroup parentView) {
      container = new RelativeLayout(parentView.getContext());
      container.setAlpha(0);
      parentView.addView(container);
      final ViewGroup.LayoutParams layoutParams = container.getLayoutParams();
      layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
      layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
      container.setLayoutParams(layoutParams);

      addMask(container);
      addProgressBar(container);
    }

    private void addMask(RelativeLayout container) {
      final View view = new View(container.getContext());
      view.setBackgroundColor(Color.WHITE);
      view.setAlpha(.5f);
      container.addView(view);
      final RelativeLayout.LayoutParams layoutParams =
          (RelativeLayout.LayoutParams) view.getLayoutParams();
      layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
      layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
      view.setLayoutParams(layoutParams);
    }

    private void addProgressBar(RelativeLayout container) {

      final ProgressBar progressBar = new ProgressBar(container.getContext());
      container.addView(progressBar);
      final RelativeLayout.LayoutParams layoutParams =
          (RelativeLayout.LayoutParams) progressBar.getLayoutParams();
      layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
      progressBar.setLayoutParams(layoutParams);
    }

    void show() {
      container.bringToFront();
      container.animate().alpha(1f).start();
    }

    void hide(Boolean animated) {
      Float noAlpha = 0f;
      if (animated) {
        container.animate().alpha(noAlpha).start();
      } else {
        container.setAlpha(noAlpha);
      }
    }
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM