2015-07-02 11 views
5

Jestem nowy w Railsach i webdev. Próba wdrożenia prostego API dla aplikacji mobilnej z Rails + Devise + Doorkeeper (jak w https://github.com/doorkeeper-gem/doorkeeper-provider-app).Powtarzalna autoryzacja daje błąd 422 z Doorkeeperem (Przepływ danych poświadczeń właściciela zasobu)

Wobec problemu, że użytkownik nie może złożyć żądania autoryzacji (POST/oauth/token), jeśli już otrzymał token. Tj .:

curl -F grant_type=password -F [email protected] -F password=12345678 -X POST http://api.to_the_trip.dev/oauth/token 

Pierwszy raz odbiorcza:

{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7ImlkIjoyLCJlbWFpbCI6IjFAdG90aGV0cmlwLmNvbSJ9fQ.dYai6nH_KYb9YbDltqwFuzCO3i0igR_gw2T7u_TeVcI","token_type":"bearer","expires_in":7200,"created_at":1435864812} 

Reklamowe idzie oauth_access_tokens tabeli (co nie jest niezbędne do JWT, ale to nie problem).

Gdybym powtarzać tę prośbę, będę odbierać i stronę błędu 422 szynach z czymś

ActiveRecord::RecordInvalid in Doorkeeper::TokensController#create 
Validation failed: Token has already been taken 

activerecord (4.2.3) lib/active_record/validations.rb:79:in `raise_record_invalid' 
activerecord (4.2.3) lib/active_record/validations.rb:43:in `save!' 
activerecord (4.2.3) lib/active_record/attribute_methods/dirty.rb:29:in `save!' 
activerecord (4.2.3) lib/active_record/transactions.rb:291:in `block in save!' 
activerecord (4.2.3) lib/active_record/transactions.rb:351:in `block in with_transaction_returning_status' 
activerecord (4.2.3) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `block in transaction' 
activerecord (4.2.3) lib/active_record/connection_adapters/abstract/transaction.rb:184:in `within_new_transaction' 
activerecord (4.2.3) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `transaction' 
activerecord (4.2.3) lib/active_record/transactions.rb:220:in `transaction' 
activerecord (4.2.3) lib/active_record/transactions.rb:348:in `with_transaction_returning_status' 
activerecord (4.2.3) lib/active_record/transactions.rb:291:in `save!' 
activerecord (4.2.3) lib/active_record/persistence.rb:51:in `create!' 
doorkeeper (2.2.1) lib/doorkeeper/models/access_token_mixin.rb:76:in `find_or_create_for' 
doorkeeper (2.2.1) lib/doorkeeper/oauth/request_concern.rb:33:in `find_or_create_access_token' 
doorkeeper (2.2.1) lib/doorkeeper/oauth/password_access_token_request.rb:30:in `before_successful_response' 
doorkeeper (2.2.1) lib/doorkeeper/oauth/request_concern.rb:7:in `authorize' 
doorkeeper (2.2.1) lib/doorkeeper/request/password.rb:19:in `authorize' 
doorkeeper (2.2.1) app/controllers/doorkeeper/tokens_controller.rb:42:in `authorize_response' 
doorkeeper (2.2.1) app/controllers/doorkeeper/tokens_controller.rb:4:in `create' 

Nawet jeśli cofnąć żeton z POST/OAuth/cofnąć, wszystko będzie takie samo, z wyjątkiem cofnięcia znacznik czasu w oauth_access_tokens. To bardzo dziwne.

ja zbadać go trochę i znaleźć kawałek kodu w odźwierny gem (access_token_mixin.rb):

def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token) 
     if Doorkeeper.configuration.reuse_access_token 
      access_token = matching_token_for(application, resource_owner_id, scopes) 
      if access_token && !access_token.expired? 
      return access_token 
      end 
     end 
     create!(
      application_id: application.try(:id), 
      resource_owner_id: resource_owner_id, 
      scopes:   scopes.to_s, 
      expires_in:  expires_in, 
      use_refresh_token: use_refresh_token 
     ) 
     end 

Tak, błąd jest w tworzenie! metoda, która mówi, że próbowaliśmy dodać duplikat (w stacktrace). A jeśli ustawię reuse_access_token w Doorkeeper.configure, to jest w porządku. Ale otrzymam ten sam token po każdej autoryzacji, co jest bardzo niepewne, co rozumiem. I tak, jeśli ręcznie usunę token z oauth_access_tokens, będę mógł autoryzować.

Co jest nie tak?

Mój odźwierny config:

Doorkeeper.configure do 
    # Change the ORM that doorkeeper will use. 
    # Currently supported options are :active_record, :mongoid2, :mongoid3, 
    # :mongoid4, :mongo_mapper 
    orm :active_record 

    resource_owner_authenticator do 
    current_user || env['warden'].authenticate!(:scope => :user) 
    end 

    resource_owner_from_credentials do |routes| 
    request.params[:user] = {:email => request.params[:username], :password => request.params[:password]} 
    request.env["devise.allow_params_authentication"] = true 
    user = request.env['warden'].authenticate!(:scope => :user) 
    env['warden'].logout 
    user 
    end 

    access_token_generator "Doorkeeper::JWT" 
end 

Doorkeeper.configuration.token_grant_types << "password" 

Doorkeeper::JWT.configure do 
#JWT config 
end 

Trasy:

require 'api_constraints' 

Rails.application.routes.draw do 
    use_doorkeeper 
    devise_for :users 
    namespace :api, defaults: {format: :json}, constraints: { subdomain: 'api' }, path: '/' do 
    scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do 
     resources :users, :only => [:show, :create, :update] 

     get '/me' => "credentials#me" 
    end 
    end 
end 

Odpowiedz

7

Dobrze, jeśli u chcą znaleźć odpowiedź a potem po prostu sformułować pytanie.

Problem polegał na domyślnym wdrożeniu tokena Doorkeeper :: JWT. Nie ma żadnej losowości w ładunku, więc zawsze było to samo dla każdego uwierzytelnienia użytkownika. Więc dodałem:

Doorkeeper::JWT.configure do 
    token_payload do |opts| 
    user = User.find(opts[:resource_owner_id]) 
    { 
     iss: "myapp", #this 
     iat: DateTime.current.utc.to_i, #this 
     rnd: SecureRandom.hex, #and this 

     user: { 
     id: user.id, 
     email: user.email 
     } 
    } 
    end 

    secret_key "key" 

    encryption_method :hs256 
end 

I działa dobrze.

5

Nie mam wystarczającej reputacji, aby skomentować wybraną odpowiedź, więc dodam kolejną odpowiedź, sugerującą poprawę.

Zamiast tworzyć roszczenie rnd, które podlega kolizjom nazw, użyj zastrzeżenia zarezerwowanego jti, ponieważ ma to na celu zapewnienie unikalnego identyfikatora JWT. Polecam również użycie wartości UUID zamiast Hex dla wartości jti.

Doorkeeper::JWT.configure do 
    token_payload do |opts| 
    user = User.find(opts[:resource_owner_id]) 
    { 
     iss: "myapp", 
     iat: DateTime.current.utc.to_i, 
     jti: SecureRandom.uuid, 

     user: { 
     id: user.id, 
     email: user.email 
     } 
    } 
    end 

    secret_key "key" 

    encryption_method :hs256 
end 

You can read more about JWT reserved claims here.

Powiązane problemy