最近在玩基于rails的api,自然有些api不能够完全开放,所以也就有了认证这一步,api认证现在一般用oauth2,现在经常看到的第三方社交登录其实就是oauth2。rails在oauth2比较受欢迎的方案就是doorkeeper,看了一下很容易使用。这里介绍一下doorkeeper的基本使用和一些需要注意的问题。
个人原创,版权所有,转载请注明出处,并保留原文链接:
https://www.embbnux.com/2016/01/26/ruby_on_rails_use_doorkeeper_for_auth2-0_to_protect_api/
一、使用doorkeeper
首先在gemfile加入
gem 'doorkeeper'
然后终端下:
rails generate doorkeeper:install rails generate doorkeeper:migration rake db:migrate
会生成配置文件和migration
还会在routes.rb里面自动加入 ‘use_doorkeeper’
这样网站就会自动多出以下接口
GET /oauth/authorize/:code GET /oauth/authorize POST /oauth/authorize DELETE /oauth/authorize POST /oauth/token POST /oauth/revoke resources /oauth/applications GET /oauth/authorized_applications DELETE /oauth/authorized_applications/:id GET /oauth/token/info
然后设置api生成权限:
编辑config/initializers/doorkeeper.rb
加入下面的代码:
resource_owner_authenticator do User.find_by(id: session[:current_user_id]) || redirect_to(login_url) end
这样访问/oauth/applications就可以新建应用了,建好应用后会有一个应用id和应用私钥,这是日后调用api要用的
二、绑定创建用户和应用
为了使新建的应用和创建的用户绑定需要做以下操作:
rails generate doorkeeper:application_owner rake db:migrate
修改配置文件加入下面:
enable_application_owner confirmation: false
新建app/controllers/oauth/applications_controller.rb:
module Oauth class ApplicationsController < Doorkeeper::ApplicationsController before_action :authenticate_user! def create @application = Doorkeeper::Application.new(application_params) @application.owner = current_user if Doorkeeper.configuration.confirm_application_owner? if @application.save flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create]) redirect_to oauth_application_url(@application) else render :new end end end end
route配置:
use_doorkeeper do controllers applications: 'oauth/applications' end
这样再次创建的应用就会有一个所有者,application.owner就是这个人
三、oauth2.0的使用
oauth2.0的认证流程有这几个:
# "authorization_code" => Authorization Code Grant Flow # "implicit" => Implicit Grant Flow # "password" => Resource Owner Password Credentials Grant Flow # "client_credentials" => Client Credentials Grant Flow
这里我启用authorization_code,password,client_credentials
在配置里面加:
resource_owner_from_credentials do |_routes| u = User.find_by(name: params[:name]) u if u && u.valid_password?(params[:password]) end grant_flows %w(authorization_code client_credentials) Doorkeeper.configuration.token_grant_types << 'password' #末尾追加
这样这三个模式就都可以用了
在api里面启用oauth2.0保护
class Api::V1::ProductsController > Api::V1::ApiController before_action :doorkeeper_authorize! # Require access token for all actions # your actions end
这样没有access_token是无法访问api的
四、oauth2.0流程测试
1、authorization_code
这个就是经常见到的第三方登录,首先跳到用户网站登录认证,再跳回应用网站。这边测试就直接用脚本实现了:
api_test.rb:
require 'oauth2' client_id = '...' client_secret = '...' redirect_uri = '...' site = "http://localhost:3000" #server client = OAuth2::Client.new(client_id, client_secret, :site => site) authorize_url = client.auth_code.authorize_url(:redirect_uri => redirect_uri) puts authorize_url
浏览器访问输出的authorize_url,登录后点击认证后会跳转到一个链接,这个链接有一个code参数,存下这个code,继续:
require 'oauth2' client_id = '...' client_secret = '...' redirect_uri = '...' site = "http://localhost:3000" #server client = OAuth2::Client.new(client_id, client_secret, :site => site) #authorize_url = client.auth_code.authorize_url(:redirect_uri => redirect_uri) puts authorize_url code = "..." # 从浏览器得到的 access_token = client.auth_code.get_token(code, :redirect_uri => redirect_uri) puts access_token.token response = access_token.get('/api/v1/users.json') JSON.parse(response.body)
输出的access_token.token就是访问api所需要的token了
2. password认证
有时候在app上调用api不可能跳转到原网站去登录,所以就得让app有用户输入密码和用户名登录后获取access_token的功能,这就是password认证。 测试api_test2.rb :
require 'oauth2' client_id = '...' client_secret = '...' redirect_uri = '...' site = "http://localhost:3000" #server client = OAuth2::Client.new(client_id, client_secret, :site => site) access_token = client.password.get_token('username', '12345678') puts access_token.token response = access_token.get('/api/v1/users.json') puts JSON.parse(response.body)
3. client_credentials
有时候api是给指定用户用的,不用登录就可以知道是谁在用,这时候就可以client_credentials,这个获取的access_token是和应用绑定的。
测试api_test3.rb :
require 'rest-client' require 'json' client_id = '...' client_secret = '...' response = RestClient.post 'http://localhost:3000/oauth/token', { grant_type: 'client_credentials', client_id: client_id , client_secret: client_secret } token = JSON.parse(response)["access_token"] puts token data = RestClient.get 'http://localhost:3000/api/v1/me.json', { 'Authorization' => "Bearer #{token}" } puts data
五、api里面的用户系统
在api里面可以通过当前的access_token之前当前登录用户:
def current_login_owner @current_login_owner ||= User.find_by(id: doorkeeper_token.resource_owner_id) if doorkeeper_token end
如果是用client_credentials方式认证的则变成下面:
@current_login_user ||= doorkeeper_token.application.owner if doorkeeper_token
结束!