Rails JSON API,令牌认证PUT请求

时间:2016-03-14 03:50:22

标签: android ruby-on-rails json authentication devise

我有我的rails Blog App使用Devise进行身份验证,JSON API使用简单的Android身份验证令牌登录/注销用户。

如果我在网络浏览器上登录,一切正常,我可以通过Http PUT进行upvote / downvote。

问题: 为了在Android中执行upvote / downvote的JSON put请求,我将登录时的身份验证令牌存储在sharedPreferences中,然后将其传回,是正确的还是我的意思是维护原始的登录http请求整个应用程序打开的时间?

在允许登录用户执行PUT之前,如何知道控制器中json的要求?我传回所有信息,我希望投票表需要,但我得到了未经授权的401错误

非常感谢。

我已经在Android应用中成功登录并在尝试upvote / downvote之前将我的身份验证令牌传回rails应用程序,但我在我的rails应用程序中将其恢复:

Started PUT "/articles/6/like" for 49.199.23.21 at 2016-03-14 01:02:44 +0000
Cannot render console from 49.199.23.21! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by ArticlesController#upvote as JSON
  Parameters: {"votes"=>{"authenticity_token"=>"jQcV-m_MSKGeNJ-SGT4N", "id"=>"3", "votable_id"=>"6", "votable_type"=>"Article", "voter_id"=>"3", "voter_type"=>"User", "vote_flag"=>"t", "vote_weight"=>"1", "created_at"=>"2016-03-14 12:02:39.882000", "updated_at"=>"2016-03-14 12:02:39.882000"}, "id"=>"6", "article"=>{}}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)

然而,如果我通过HTML来实现这一点(我注意到身份验证令牌的时间要长得多):

Started PUT "/articles/5/like" for 137.147.172.125 at 2016-03-12 09:34:43 +0000
Cannot render console from 137.147.172.125! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by ArticlesController#upvote as HTML
  Parameters: {"authenticity_token"=>"4eC8xgC+1CBb51gKO7ZRzjL1BGOF6TYTnbqQcJ3evxvpmGcguYHBNYSn9v3LJMfoo3F29frntwzgyLoeI9oaLg==", "id"=>"5"}
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ?  ORDER BY "users"."id" ASC LIMIT 1  [["id", 1]]
  Article Load (0.1ms)  SELECT  "articles".* FROM "articles" WHERE "articles"."id" = ?  ORDER BY "articles"."created_at" DESC LIMIT 1  [["id", 5]]
   (0.3ms)  SELECT COUNT(*) FROM "votes" WHERE "votes"."votable_id" = ? AND "votes"."votable_type" = ? AND "votes"."voter_id" = ? AND "votes"."voter_type" = ? AND "votes"."vote_scope" IS NULL  [["votable_id", 5], ["votable_type", "Article"], ["voter_id", 1], ["voter_type", "User"]]
   (0.2ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "votes" ("votable_id", "votable_type", "voter_id", "voter_type", "vote_flag", "vote_weight", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?)  [["votable_id", 5], ["votable_type", "Article"], ["voter_id", 1], ["voter_type", "User"], ["vote_flag", "t"], ["vote_weight", 1], ["created_at", "2016-03-12 09:34:43.393685"], ["updated_at", "2016-03-12 09:34:43.393685"]]
   (11.4ms)  commit transaction
Redirected to https://completerubyonrailscourse-adam1st.c9users.io/articles/5
Completed 302 Found in 187ms (ActiveRecord: 13.2ms)

通过Android登录:

    Started POST "/users/sign_in" for 49.199.23.21 at 2016-03-14 01:01:31 +0000
Cannot render console from 49.199.23.21! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by SessionsController#create as JSON
  Parameters: {"user"=>{"email"=>"adam1st@hotmail.com", "password"=>"[FILTERED]"}, "session"=>{"user"=>{"email"=>"adam1st@hotmail.com", "password"=>"[FILTERED]"}}}
Can't verify CSRF token authenticity
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ?  ORDER BY "users"."id" ASC LIMIT 1  [["email", "adam1st@hotmail.com"]]
   (0.2ms)  begin transaction
  SQL (0.6ms)  UPDATE "users" SET "last_sign_in_at" = ?, "current_sign_in_at" = ?, "sign_in_count" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["last_sign_in_at", "2016-03-14 00:59:01.022130"], ["current_sign_in_at", "2016-03-14 01:01:31.196495"], ["sign_in_count", 28], ["updated_at", "2016-03-14 01:01:31.198628"], ["id", 3]]
   (13.9ms)  commit transaction
   (0.1ms)  begin transaction
   (0.2ms)  SELECT COUNT(*) FROM "users" WHERE "users"."authentication_token" = ?  [["authentication_token", "jQcV-m_MSKGeNJ-SGT4N"]]
  SQL (0.3ms)  UPDATE "users" SET "authentication_token" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["authentication_token", "jQcV-m_MSKGeNJ-SGT4N"], ["updated_at", "2016-03-14 01:01:31.219386"], ["id", 3]]
   (10.8ms)  commit transaction
Completed 200 OK in 166ms (Views: 0.5ms | ActiveRecord: 26.5ms)

路由文件:

Rails.application.routes.draw do

  devise_for :users, :controllers => {registrations: "registrations", sessions: "sessions", :omniauth_callbacks => "callbacks"}
  # The priority is based upon order of creation: first created -> highest priority.
  # See how all your routes lay out with "rake routes".

  # You can have the root of your site routed with "root"
  root to: 'articles#index'
  resources :articles do
    member do
      put "like", to: "articles#upvote"
      put "dislike", to: "articles#downvote"
    end
    resources :comments
  end

使用upvote / downvote的ArticlesController:

class ArticlesController < ApplicationController
  before_action :authenticate_user!, :except => [:index, :show]
  before_filter :set_article, only: [:show, :edit, :update, :destroy, :upvote, :downvote]
  skip_before_filter :verify_authenticity_token, :only => [:upvote, :downvote]

  def index
    @articles = Article.all
  end

  def new
    @article = Article.new
  end

  def create
    @article = current_user.articles.build(article_params)
    if @article.save
      flash[:success] = "Article has been created"
      redirect_to articles_path
    else
      flash.now[:danger] = "Article has not been created"
      render :new
    end
  end

  def edit
    if @article.user != current_user
      flash[:danger] = "You can only edit your own article"
      redirect_to root_path
    end
  end 

  def update
    if @article.user != current_user
      flash[:danger] = "You can only edit your own article"
      redirect_to root_path
    else
      if @article.update(article_params)
          flash[:success] = "Article has been updated"
          redirect_to @article
      else
        flash.now[:danger] = "Article has not been updated"
        render :edit
      end
    end
  end

  def show
    @comment = @article.comments.build
  end 

  def destroy
    if @article.destroy
      flash[:success] = "Article has been deleted"
      redirect_to articles_path
    end
  end
  def upvote
    @article.upvote_by current_user
    flash[:success] = "Successfully liked"
    respond_to do |format|
      format.html {redirect_to :back }
      format.json { render json: { count: @article.liked_count } }
    end
  end
  def downvote
    @article.downvote_by current_user
    flash[:success] = "Successfully disliked"
    respond_to do |format|
      format.html {redirect_to :back }
      format.json { render json: { count: @article.disliked_count } }
    end
  end

  private 
    def article_params
      params.require(:article).permit(:title, :body)
    end

    def set_article
    @article=Article.find(params[:id])
    end
end

SessionsController:

class SessionsController < Devise::SessionsController

  def create
   self.resource = warden.authenticate!(auth_options)
   sign_in(resource_name, resource)

   current_user.update authentication_token: nil

   respond_to do |format|
     format.json { render :json => {  :user => current_user, :status => :ok, :authentication_token => current_user.authentication_token } }
     format.html { super }
   end
  end

  # DELETE /resource/sign_out
  def destroy

   respond_to do |format|
     format.json {
       if current_user
         current_user.update authentication_token: nil
         signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
         render :json => {}.to_json, :status => :ok
       else
         render :json => {}.to_json, :status => :unprocessable_entity
       end  

     }

     format.html{
      super
    } 
   end
  end
end

Android中的投票活动:

private class VoteTask extends UrlJsonAsyncTask {
        public VoteTask(Context context) {
            super(context);
        }

        @Override
        protected JSONObject doInBackground(String... urls) {
            HttpClient client = new DefaultHttpClient();
            HttpPut put = new HttpPut(urls[0]);
            JSONObject holder = new JSONObject();
            JSONObject voteObj = new JSONObject();
            String response = null;
            JSONObject json = new JSONObject();

            try {
                try {
                    // setup the returned values in case
                    // something goes wrong
                    json.put("success", false);
                    json.put("info", "Something went wrong. Retry!");
                    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                    String id = sharedPreferences.getString("userId", "default value");
                    String authToken = sharedPreferences.getString("AuthToken", "default value");

                    voteObj.put("authenticity_token",authToken);
                    voteObj.put("id",id);

                    voteObj.put("votable_id",articleId);
                    voteObj.put("votable_type", "Article");
                    voteObj.put("voter_id", id);
                    voteObj.put("voter_type", "User");
                    voteObj.put("vote_flag", voteFlag);
                    voteObj.put("vote_weight", "1");
                    voteObj.put("created_at", strDate);
                    voteObj.put("updated_at", strDate);
                    holder.put("votes", voteObj);
                    StringEntity se = new StringEntity(holder.toString());
                    put.setEntity(se);

                    // setup the request headers
                    put.setHeader("Accept", "application/json");
                    put.setHeader("Content-Type", "application/json");

                    //ResponseHandler<String> responseHandler = new BasicResponseHandler();
                    //response = client.execute(post, responseHandler);
                    //json = new JSONObject(response);

                    response = String.valueOf(client.execute(put));
                    json = new JSONObject(response);


                /*} catch (HttpResponseException e) {
                    e.printStackTrace();
                    Log.e("ClientProtocol", "" + e);
                    json.put("info", "Email and/or password are invalid. Retry!");*/
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.e("IO", "" + e);
                }
            } catch (JSONException e) {
                e.printStackTrace();
                Log.e("JSON", "" + e);
            }

            return json;
        }

Android中正在运行的登录活动:

private class LoginTask extends UrlJsonAsyncTask {
        public LoginTask(Context context) {
            super(context);
        }

        @Override
        protected JSONObject doInBackground(String... urls) {
            webClient = new DefaultHttpClient();
            HttpPost post = new HttpPost(urls[0]);
            JSONObject holder = new JSONObject();
            JSONObject userObj = new JSONObject();
            String response = null;
            JSONObject json = new JSONObject();

            try {
                try {
                    // setup the returned values in case
                    // something goes wrong
                    json.put("success", false);
                    json.put("info", "Something went wrong. Retry!");
                    // add the user email and password to
                    // the params
                    userObj.put("email", username);
                    userObj.put("password", password);
                    holder.put("user", userObj);
                    StringEntity se = new StringEntity(holder.toString());
                    post.setEntity(se);

                    // setup the request headers
                    post.setHeader("Accept", "application/json");
                    post.setHeader("Content-Type", "application/json");

                    ResponseHandler<String> responseHandler = new BasicResponseHandler();
                    response = webClient.execute(post, responseHandler);
                    json = new JSONObject(response);

                } catch (HttpResponseException e) {
                    e.printStackTrace();
                    Log.e("ClientProtocol", "" + e);
                    json.put("info", "Email and/or password are invalid. Retry!");
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.e("IO", "" + e);
                }
            } catch (JSONException e) {
                e.printStackTrace();
                Log.e("JSON", "" + e);
            }

            return json;
        }

投票架构:

    create_table "votes", force: :cascade do |t|
    t.integer  "votable_id"
    t.string   "votable_type"
    t.integer  "voter_id"
    t.string   "voter_type"
    t.boolean  "vote_flag"
    t.string   "vote_scope"
    t.integer  "vote_weight"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "votes", ["votable_id", "votable_type", "vote_scope"], name: "index_votes_on_votable_id_and_votable_type_and_vote_scope"
  add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope"

1 个答案:

答案 0 :(得分:0)

我使用了简单令牌身份验证https://github.com/gonzalo-bulnes/simple_token_authentication的git并看到了这一点。

身份验证方法2:请求标头

您还可以使用请求标头(在对API进行身份验证时可能更简单):

X-User-Email alice@example.com

X-User-Token 1G8_s7P-V-4MGojaKD7a

因此,在执行投票之前,我将其放在标题中的投票任务中并且有效。

我现在也知道我不需要维护原始的http请求,我只是在每个请求之前放入两个标头来证明我是授权用户。

相关问题