我有我的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"
答案 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请求,我只是在每个请求之前放入两个标头来证明我是授权用户。