我正在尝试使devise
和devise-jwt
宝石正常工作,以便可以在仅API的Rails应用程序中实现授权。
我已经安装了devise
和devise-jwt
宝石。
我按照此博客文章中的说明进行操作:
https://medium.com/@mazik.wyry/rails-5-api-jwt-setup-in-minutes-using-devise-71670fd4ed03
我实现了作者在其帖子中包含的要求规范,但我无法让它们通过。如果我在会话控制器中放置一个Byebug,就会看到“用户需要登录或注册才能继续。”
任何关于我做错事情的想法吗?
以下是相关文件:
route.rb
Rails.application.routes.draw do
namespace :api, path: '', defaults: {format: :json} do
namespace :v1 do
devise_for :users,
path: '',
path_names: {
sign_in: 'signin',
sign_out: 'signout',
registration: 'signup'
}
...
end
end
controllers / api / v1 / sessions_controller.rb
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
private
def respond_with(resource, _opts = {})
render json: resource
end
def respond_to_on_destroy
head :no_content
end
end
型号/user.rb
class User < ApplicationRecord
devise :confirmable, :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :jwt_authenticatable, jwt_revocation_strategy: JwtBlacklist
...
end
models / jwt_blacklist.rb
class JwtBlacklist < ApplicationRecord
include Devise::JWT::RevocationStrategies::Blacklist
self.table_name = 'jwt_blacklist'
end
config / initializers / devise.rb
Devise.setup do |config|
# Setup for devise JWT token authentication
config.jwt do |jwt|
jwt.secret = Rails.application.secret_key_base
jwt.dispatch_requests = [
['POST', %r{^*/signin$}]
]
jwt.revocation_requests = [
['DELETE', %r{^*/signout$}]
]
jwt.expiration_time = 1.day.to_i
end
config.navigational_formats = []
...
end
spec / request / authentication_spec.rb
require 'rails_helper'
describe 'POST /v1/signin', type: :request do
let(:user) { create(:user) }
let(:url) { '/v1/signin' }
let(:params) do
{
user: {
email: user.email,
password: user.password
}
}
end
context 'when params are correct' do
before do
post url, params: params
end
it 'returns 200' do
expect(response).to have_http_status(200)
end
it 'returns JTW token in authorization header' do
expect(response.headers['Authorization']).to be_present
end
it 'returns valid JWT token' do
decoded_token = decoded_jwt_token_from_response(response)
expect(decoded_token.first['sub']).to be_present
end
end
context 'when login params are incorrect' do
before { post url }
it 'returns unathorized status' do
expect(response.status).to eq 401
end
end
end
describe 'DELETE /v1/signout', type: :request do
let(:url) { '/v1/signout' }
it 'returns 204, no content' do
delete url
expect(response).to have_http_status(204)
end
end
我希望测试能够通过,但是出现以下错误:
测试失败
Failures:
1) POST /v1/signin when params are correct returns 200
Failure/Error: expect(response).to have_http_status(200)
expected the response to have status code 200 but it was 401
# ./spec/request/authentication_spec.rb:21:in `block (3 levels) in <top (required)>'
2) POST /v1/signin when params are correct returns JTW token in authorization header
Failure/Error: expect(response.headers['Authorization']).to be_present
expected `nil.present?` to return true, got false
# ./spec/request/authentication_spec.rb:25:in `block (3 levels) in <top (required)>'
3) POST /v1/signin when params are correct returns valid JWT token
Failure/Error: decoded_token = decoded_jwt_token_from_response(response)
NoMethodError:
undefined method `decoded_jwt_token_from_response' for #<RSpec::ExampleGroups::POSTV1Signin::WhenParamsAreCorrect:0x00007fec3d3ae158>
# ./spec/request/authentication_spec.rb:29:in `block (3 levels) in <top (required)>'
Finished in 0.76386 seconds (files took 3.31 seconds to load)
5 examples, 3 failures
Failed examples:
rspec ./spec/request/authentication_spec.rb:20 # POST /v1/signin when params are correct returns 200
rspec ./spec/request/authentication_spec.rb:24 # POST /v1/signin when params are correct returns JTW token in authorization header
rspec ./spec/request/authentication_spec.rb:28 # POST /v1/signin when params are correct returns valid JWT token
答案 0 :(得分:1)
我不知道您是否找到了解决方案;但是我留下了我已经采取的方法;可能会有帮助。
对问题特别注意,解决方案是更改:
decoded_token = decoded_jwt_token_from_response(response)
收件人:
decoded_token = JWT.decode(response.headers['authorization'].split(' ').last, Rails.application.credentials.jwt_secret, true)
因为我在文档或其他地方找不到任何内容,所以我选择使用JWT提供的方法进行解码。 另外,如果您看到我以不同的方式处理请求,但我认为那根本不是问题。
require 'rails_helper'
require "json"
RSpec.describe "POST /login", type: :request do
let(:user) { User.create!( username: 'usertest',
email: 'usertest@email.com',
password: 'passwordtest123',
password_confirmation: 'passwordtest123') }
let(:url) { '/users/login' }
let(:params) do
{
user: {
login: user.email,
password: user.password
}
}
end
context 'when params are correct' do
before do
post url, params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
end
it 'returns 200' do
expect(response).to have_http_status(200)
end
it 'returns JTW token in authorization header' do
expect(response.headers['authorization']).to be_present
end
it 'returns valid JWT token' do
token_from_request = response.headers['Authorization'].split(' ').last
decoded_token = JWT.decode(token_from_request, Rails.application.credentials.jwt_secret, true)
expect(decoded_token.first['sub']).to be_present
end
end
context 'when login params are incorrect' do
before { post url }
it 'returns unathorized status' do
expect(response.status).to eq 401
end
end
end
RSpec.describe 'DELETE /logout', type: :request do
let(:url) { '/users/logout' }
it 'returns 204, no content' do
delete url
expect(response).to have_http_status(204)
end
end
RSpec.describe 'POST /signup', type: :request do
let(:url) { '/users/signup' }
let(:params) do
{
user: {
username: 'usertest2',
email: 'usertest2@email.com',
password: 'passwordtest123',
password_confirmation: 'passwordtest123'
}
}
end
context 'when user is unauthenticated' do
before {
post url,
params: params.to_json,
headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
}
it 'returns 201' do
expect(response.status).to eq 201
end
it 'returns a new user' do
expect(response).to have_http_status :created
end
end
context 'when user already exists' do
before do
post url,
params: params.to_json,
headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
post url,
params: params.to_json,
headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
end
it 'returns bad request status' do
expect(response.status).to eq 400
end
it 'returns validation errors' do
expect(response_body['errors'].first['title']).to eq('Bad Request')
end
end
end
PD:我保留了规范代码进行注册,但有一些区别(用户模型中的请求,URL,用户名参数(这就是为什么我在登录请求中使用登录参数的原因)),我在一个单独的规范中做了全部说明。 rb文件,...)到https://medium.com/@mazik.wyry/rails-5-api-jwt-setup-in-minutes-using-devise-71670fd4ed03。记住这一点。
答案 1 :(得分:0)
我相信您需要在请求授权之前使用助手sign_in user
。检查https://github.com/heartcombo/devise,控制器测试