3
  • omniauth-google-oauth2我有一个用于使用 Google 帐户进行身份验证的 Web 应用程序。然后使用 cookie ( session[:user_id]) 保持会话。这行得通。
  • 我还有一个使用 REST/JSON API 的网站服务的 Android 应用程序。应用程序用于AccountManager获取访问令牌。这也有效。

我试图让 Android 应用程序发出一个请求,使 Web 应用程序验证该令牌并启动与 Android 应用程序的会话(cookie 在 Android 应用程序上是持久的)。

我注意到来自 Google 的回调 URL 是:http://my.domain.com/auth/google_oauth2/callback,所以我尝试添加参数state="/"code="<TOKEN FROM AccountManager>".

这导致:

(google_oauth2) Callback phase initiated.
(google_oauth2) Authentication failure! invalid_credentials: OmniAuth::Strategies::OAuth2::CallbackError, OmniAuth::Strategies::OAuth2::CallbackError

Started GET "/auth/google_oauth2/callback?state=%2F&code=<TOKEN FROM AccountManager>" for 5.102.217.111 at 2013-08-10 18:38:58 +0300

OmniAuth::Strategies::OAuth2::CallbackError (OmniAuth::Strategies::OAuth2::CallbackError):
  omniauth-oauth2 (1.1.1) lib/omniauth/strategies/oauth2.rb:71:in `callback_phase'
  omniauth (1.1.4) lib/omniauth/strategy.rb:226:in `callback_call'
  omniauth (1.1.4) lib/omniauth/strategy.rb:182:in `call!'
  omniauth (1.1.4) lib/omniauth/strategy.rb:164:in `call'
  omniauth (1.1.4) lib/omniauth/builder.rb:49:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'
  rack (1.4.5) lib/rack/etag.rb:23:in `call'
  rack (1.4.5) lib/rack/conditionalget.rb:25:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/head.rb:14:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/params_parser.rb:21:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/flash.rb:242:in `call'
  rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'
  rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/cookies.rb:341:in `call'
  activerecord (3.2.13) lib/active_record/query_cache.rb:64:in `call'
  activerecord (3.2.13) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `_run__1566251561940761300__call__2926332968477140393__callbacks'
  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `__run_callback'
  activesupport (3.2.13) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'
  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/reloader.rb:65:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'
  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'
  railties (3.2.13) lib/rails/rack/logger.rb:16:in `block in call'
  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'
  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'
  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'
  rack (1.4.5) lib/rack/runtime.rb:17:in `call'
  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'
  rack (1.4.5) lib/rack/lock.rb:15:in `call'
  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'
  railties (3.2.13) lib/rails/engine.rb:479:in `call'
  railties (3.2.13) lib/rails/application.rb:223:in `call'
  rack (1.4.5) lib/rack/content_length.rb:14:in `call'
  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'
  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'
  /home/oded/.rvm/rubies/ruby-2.0.0-p195/lib/ruby/2.0.0/webrick/httpserver.rb:138:in `service'
  /home/oded/.rvm/rubies/ruby-2.0.0-p195/lib/ruby/2.0.0/webrick/httpserver.rb:94:in `run'
  /home/oded/.rvm/rubies/ruby-2.0.0-p195/lib/ruby/2.0.0/webrick/server.rb:295:in `block in start_thread'


  Rendered /home/oded/.rvm/gems/ruby-2.0.0-p195/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (0.8ms)
  Rendered /home/oded/.rvm/gems/ruby-2.0.0-p195/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (0.7ms)
  Rendered /home/oded/.rvm/gems/ruby-2.0.0-p195/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (5.3ms)

Web 和 Android 应用程序均在 Google API 控制台中获得授权。

我不想使用GoogleAuthUtil有 3 个原因:我已经完全实现了 AccountManager,它至少使我的应用程序大小翻了一番,而且我看不出它如何解决我的网络应用程序的问题。

作为一个临时解决方案,我创建了一个 WebView 活动,该活动与 Web 应用程序经历相同的过程。这不是一个好的解决方案,因为用户需要输入他的电子邮件和密码,而他无法判断这是否是网络钓鱼。

宝石文件:

gem 'omniauth-google-oauth2'

会话控制器(app/controllers/sessions_controller.rb):

def new
  redirect_to "/auth/google_oauth2"
end

def create
  auth = request.env["omniauth.auth"]
  account = case auth['provider']
    when GoogleAccount::PROVIDER then GoogleAccount.find_by_omniauth(auth)
  end
  session[:user_id] = account.user.id
  respond_to do |format|
    format.html { redirect_to root_url, :notice => "Signed in!" }
    format.json { render json: { result: true } }
  end
end

谷歌账户(app/models/google_account.rb):

after_validation :create_user!, on: :create

def self.find_by_omniauth(auth)
  google_account = find_by_uid(auth['uid']) || new(uid: auth['uid'])
  google_account.attributes = auth['info'].select { |k, v| k.in?(attribute_names) }
  google_account.save!
  google_account
end

会话管理器.java:

public void login() {
    _accountManager = AccountManager.get(_activity);
    final Account[] accounts = _accountManager.getAccountsByType("com.google");
    String[] accountNames = new String[accounts.length];
    for (int i = 0; i < accounts.length; i++) {
        accountNames[i] = accounts[i].name;
    }

    new AlertDialog.Builder(_activity).
    setItems(accountNames, new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            _account = accounts[which];
            getAuthToken();
        }
    }).
    show();
}

private void getAuthToken() {
    _accountManager.getAuthToken(
            _account, "ah", null, _activity,
            new AccountManagerCallback<Bundle>() {
                @Override
                public void run(AccountManagerFuture<Bundle> result) {
                    Bundle bundle;
                    try {
                        bundle = result.getResult();
                    } catch (OperationCanceledException e) {
                        // TODO: handle errors
                        e.printStackTrace();
                        return;
                    } catch (AuthenticatorException e) {
                        // TODO: handle errors
                        e.printStackTrace();
                        return;
                    } catch (IOException e) {
                        // TODO: handle errors
                        e.printStackTrace();
                        return;
                    }

                    Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT);
                    if (launch != null) {
                        _activity.startActivityForResult(launch, _accountManagerRequestCode);
                        return;
                    }

                    onGetAuthToken(bundle);
                }
            },
            null
            );
}

public void onAccountManagerResult(int resultCode, Intent data) {
    switch (resultCode) {
    case Activity.RESULT_OK:
        _accountManager = AccountManager.get(_activity);
        getAuthToken();
        break;
    }
}

private void onGetAuthToken(Bundle bundle) {
    final String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
    login(
            token,
            new LoginResponseHandler() {
                @Override
                public void onResult(boolean result, boolean hadAnonymous, String message, Map<String, List<String>> errors) {
                    if (result) {
                        setLoggedIn(true);
                    } else {
                        // Token expired?
                        _accountManager.invalidateAuthToken("com.google", token);
                        getAuthToken();
                    }
                }
            });
}

public static void login(String token, LoginResponseHandler loginResponseHandler) {
    RequestParams requestParams = defaultRequestParams();
    requestParams.put("code", token);
    requestParams.put("state", "/");
    _client.get(
            BASE_URL + "/auth/google_oauth2/callback", requestParams,
            new JsonLoginResponseHandler(loginResponseHandler));
}
4

2 回答 2

1

我设法手动验证服务器端的令牌,而不使用omniauth。

  • 我不得不将呼叫更改为AccountManager#getAuthToken. 参数(authTokenType以前"ah")需要填写客户端 ID,如下所示:"audience:server:client_id:" + CLIENT_ID. 客户端 ID 是从 API 控制台检索的,属于您的 Web 应用程序。它看起来像这样:123456789.apps.googleusercontent.com
  • 服务器端的代码很简单,所以我不介意:

会话控制器(app/controllers/sessions_controller.rb):

def create
  auth = request.env["omniauth.auth"]
  if auth
    account = case auth['provider']
                when GoogleAccount::OAUTH_PROVIDER then GoogleAccount.find_by_omniauth(auth)
              end
  else
    account = case params[:provider]
                when GoogleAccount::PROVIDER then GoogleAccount.find_by_token(params[:token])
              end
  end
  render text: "Unauthoirized", status: :unauthorized and return if account.nil?
  session[:user_id] = account.user.id
    respond_to do |format|
    format.html { redirect_to root_url, :notice => "Signed in!" }
    format.json { render json: { result: true } }
  end
end

谷歌账户(app/models/google_account.rb):

def self.find_by_token(token)
  validator = GoogleIDToken::Validator.new
  jwt = validator.check(token, CLIENT_ID)
  return if jwt.nil?

  google_account = where(uid: jwt['id']).first_or_initialize
  google_account.email = jwt['email']
  google_account.save!
  google_account
end

jwt是一个看起来像这样的哈希:

{"iss"=>"accounts.google.com",
 "verified_email"=>"true",
 "email_verified"=>"true",
 "email"=>"my.email@gmail.com",
 "aud"=>"123456789.apps.googleusercontent.com", # aka CLIENT_ID
 "cid"=>"123456789-somerandomletters.apps.googleusercontent.com", # this is the device ID, you can validate that too with another parameter
 "azp"=>"123456789-samerandomletters.apps.googleusercontent.com",
 "id"=>"10000000000000000000", # this is Google's user ID for this user, keep it
 "sub"=>"10000000000000000000",
 "iat"=>1376306389,
 "exp"=>1376310289}

服务器现在无法获得与登录 Web 时相同的详细信息,因为令牌仅保存电子邮件,但这对我来说已经足够了。我也不验证 CID(设备 ID),不确定你什么时候需要这个。

于 2013-08-12T12:04:46.840 回答
1

使用 Google Play 服务,您可以调用 GoogleAuthUtil.getToken 来获取 Android 应用程序中的令牌。请参阅http://developer.android.com/google/play-services/auth.html

我使用 rest-client 手动验证服务器上的令牌。我基于我过去使用的 Twitter OAuth Echo 的标题。

require 'rest_client'
require 'json'

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  before_filter :require_login_or_oauth!

  private

  def authorization_header
    'X-Verify-Credentials-Authorization'
  end

  def service_provider_header
    'X-Auth-Service-Provider'
  end

  def verify_url
    'https://www.googleapis.com/oauth2/v1/tokeninfo'
  end

  def client_ids
      %w(-- your client ids here--)
  end

  def find_oauth_user
    if request.headers[authorization_header].nil? or verify_url != request.headers[service_provider_header]
      logger.warn "bad authorization headers"
      render :text => "Bad authorization headers", :status => :unauthorized
      return nil
    end
    begin
      response = RestClient.get verify_url, { :params => { :access_token => request.headers[authorization_header] }, :accept => :json }
      if 200 != response.code
        render :text => "OAuth validation failed", :status => :unauthorized
        return nil
      end
      body = JSON.parse(response.to_str)

      return nil if not client_ids.include? body['audience']
      # TODO log bad client

      # TODO possibly validate scope

      if identity = Identity.find_by_uid(body['user_id'])
        user = identity.user
        logger.info "Authenticated #{user.id} as #{identity.email}"
        return user
      end
      return nil
    rescue RestClient::Exception => e
      logger.warn e
      render :text => "OAuth validation failed", :status => :unauthorized
      return nil
    end
  end

  def require_login_or_oauth!
    if "application/json" == request.format
      user = find_oauth_user
      if user
        sign_in(:user, user)
        session[:user_id] = user.id
      else
        redirect_to root_url, status: 401
      end
    else
      authenticate_user!
    end
  end
end
于 2013-08-18T16:50:38.200 回答