【问题标题】:Rails JSON API, token authentication PUT requestRails JSON API,令牌认证 PUT 请求
【发布时间】:2016-03-14 03:50:21
【问题描述】:

我的 Rails 博客应用程序使用 Devise 进行身份验证,使用 JSON API 使用 Android 的简单身份验证令牌登录/注销用户。

如果我在网络浏览器上登录,一切正常,我可以执行 Http PUT 来支持/反对。

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

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

非常感谢。

我已经在 Android 应用程序中成功登录并将我的身份验证令牌传递回 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

带有赞成/反对票的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

会话控制器:

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"

【问题讨论】:

  • 我注意到它抱怨这些连接不是来自localhost;您是否尝试过使用选项 --binding=0.0.0.0 运行服务器?
  • 每次运行 rails 服务器时,我都会使用 0.0.0.0 绑定它:rails s -b 0.0.0.0 -p 8080(我在 cloud9 上运行)

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


【解决方案1】:

我去Simple token authenticationhttps://github.com/gonzalo-bulnes/simple_token_authentication的git看到了这个。

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

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

X-User-Email alice@example.com

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

所以我在投票之前把它放在标题中的投票任务中,它起作用了。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-04
    • 2016-11-02
    • 1970-01-01
    • 2013-05-29
    • 1970-01-01
    • 1970-01-01
    • 2014-11-27
    相关资源
    最近更新 更多