JWTのRubyでの使用概要とSinatraフレームワークの解読とインタフェースの構築

14760 ワード

JSON Web TokenはJSON Web Tokenの基本原理を紹介し,次にこの文章はRuby分析と結びつけて実際のプロジェクトでの使用をまとめた.Rubyには対応するバージョンのGem ruby-jwtがあり、この論文ではJWTのRubyプログラミング言語での使用を議論し、Sinatraフレームワークを簡単に解析します.
ruby-jwt紹介
ruby-jwtはRFC 7519 OAuth JSON Web Token規格を実現し、文書と結びつけてRubyでJWTをどのように使用するかをまとめた.
暗号化とチェック
jwtでは通常、RSAおよびHMACにそれぞれ対応する2つの暗号化方式RS 256およびHS 256を使用する.サードパーティにHTTP APIインタフェースを提供する場合、通常はJWTを使用して明文の検証と暗号化の特性を使用します.RS 256を使用する場合は両方とも相手に自分のOpenSSL公開鍵を提供する必要があり、HS 256を使用する場合は同じ鍵列を使用することを協議する.
RS 256を使用するプロセスは:甲のサーバーは自分の秘密鍵でデータに対して署名を行って、それから乙は甲の提供した公開鍵を使って署名を検査してそして明文を解析してそして自分の秘密鍵を使って署名をプラスして乙のサーバーの明文に返して、甲のサーバーは更に乙の提供した公開鍵を使って署名を解除します.
HS 256を使用するプロセスは、双方が合意した鍵で転送する必要がある明文を復号し、相手から送信されたJWT token ruby-jwt暗号化方法のソースコードを検証し、署名することである.
def encode(payload, key, algorithm = 'HS256', header_fields = {})
  encoder = Encode.new payload, key, algorithm, header_fields
  encoder.segments
end

def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
  raise(JWT::DecodeError, 'Nil JSON web token') unless jwt

  merged_options = DEFAULT_OPTIONS.merge(custom_options)

  decoder = Decode.new jwt, verify
  header, payload, signature, signing_input = decoder.decode_segments
  decode_verify_signature(key, header, payload, signature, signing_input, merged_options, &keyfinder) if verify

  Verify.verify_claims(payload, merged_options)

  raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload

  [payload, header]
end

encodeとdecodeのソースコードを組み合わせて、署名と検証の例を学びます.
require 'jwt'

payload = { data: 'test' }

# HS256 encode
hmac_secret = 'my$ecretK3y'
token = JWT.encode payload, hmac_secret, 'HS256'
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.ZxW8go9hz3ETCSfxFxpwSkYg_602gOPKearsf6DsxgY

# HS256 decode
decoded_token = JWT.decode token, hmac_secret, true, { :algorithm => 'HS256' }
# [{"data"=>"test"}, {"typ"=>"JWT", "alg"=>"HS256"}]

rsa_private = OpenSSL::PKey::RSA.generate 2048
rsa_public = rsa_private.public_key

# RS256 encode
token = JWT.encode payload, rsa_private, 'RS256'
# eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.vtkMG9wCJRWc8lwOQJxnV8vRWRiBfvIzsE-vnM168Pe4jXszc2p9_2upAi8SI5EuwR7CsVAPFO_SqNsJLCb_55srqyBxPAyy97gy-44VyFR-dsnt9xpt2meJ4DyolXwhWxHTF9WkmQPHoFlu_2ssOOszBj9MO1X7KhmgkrX9h9yBTTzT9qvZQkesAbZz1RrF3ZRhihwbBdtGCCbJvlGBI6NAoMf_b3vNqeaHawc5hMS9nfoN-5Sc9CleJdPPnWnN7OYXeI_xdhCNTok0b7nMPvgDuIj9nTW4_u3Fv-rq9IiM-62LU1JFdqFVWQU-f72nkbT4bVy_SJr11mJ9Q6pXNQ

# RS decode
decoded_token = JWT.decode token, rsa_public, true, { :algorithm => 'RS256' }
[{"data"=>"test"}, {"typ"=>"JWT", "alg"=>"RS256"}]

いくつかのよく使われるClaim
  • Expiration Time Claim(exp)失効時間
  • Issued At Claim(iat)は、このJWT tokenの発行時間
  • を表す
  • JWT ID Claim JWT token一意ID
  • hmac_secret = 'my$ecretK3y'
    
    exp = Time.now.to_i + 4 * 3600
    iat = Time.now.to_i
    jti_raw = [hmac_secret, iat].join(':').to_s
    jti = Digest::MD5.hexdigest(jti_raw)
    
    payload = { :data => 'data', :exp => exp, :iat => iat, :jti => jti}
    token = JWT.encode payload, hmac_secret, 'HS256'
    
    # decode
    begin
      decoded_token = JWT.decode token, hmac_secret, true, { :verify_iat => true, :verify_jti => true, :algorithm => 'HS256' }
    rescue JWT::ExpiredSignature
      # Handle expired token, e.g. logout user or deny access
    rescue JWT::InvalidIatError
      # Handle invalid token, e.g. logout user or deny access
    rescue JWT::InvalidJtiError
      # Handle invalid token, e.g. logout user or deny access
    end
    

    Sinatra原理の解釈、使用
    このセクションでは、Sinatraの基本的な実装原理と、Sinatra拡張原理を実装する方法について説明します.そして完全なSinatraアプリケーションを構築します.仮定したシナリオは,我々がサードパーティにHTTP APIを提供し,JWTの2つの特性を用いた:明文暗号化とチェックアウトである.実際の開発では双方ともOpenSSL生成の公開鍵を提供する必要がある.
    Sinatraの基本原理
    記述規則
  • top-level DSLはSinatraにおけるルーティング(Railsにおけるcontroller)
  • を指す.
  • classic style文で古典的なスタイルを記述するSinatraアプリケーション
  • module style文では、モジュール化されたスタイルのSinatraアプリケーション
  • として記述されている.
  • Sinatra.helpersが実装した拡張子は、「ヘルプメソッド拡張」
  • です.
  • Sinatra.registerが実現する拡張は「ルーティング拡張」
  • である.
    クラシックスタイルとモジュール化スタイルのSinatra応用
    通常、Sinatraを使用してプロジェクトを構築するには、クラシックスタイルとモジュール化スタイルの2つの方法があります.
    1つは古典的なスタイル(classic style)です.このモードは通常、HTTPリクエストを受け入れるすべてのルーティングが1つのファイルにのみ適用されます(app.rb).app.rbファイルにはrequire 'sinatra'を導入し、残りは直接ルーティングを書く必要があります.
    # app.rb
    require 'sinatra'
    
    get '/' do
      'Hello world'
    end
    

    もう1つは,モジュール化スタイル(Modular style)というモデルも本論文で主に紹介する方法であり,通常,クラスRailsのMVC構造に拡張できる大型Rack−baseアプリケーションに適用され,多重化可能なRackミドルウェアを記述する.このような足場により,比較的複雑なインタフェースアプリケーションを実現できる.
    
    #          sinatra/base     sinatra
    require 'sinatra/base'
    require "sinatra/config_file"
    
    class MyApp < Sinatra::Base
      register Sinatra::ConfigFile
      config_file 'path/to/config.yml'
    
      get '/' do
        @greeting = settings.greeting
        haml :index
      end
    
      # The rest of your modular application code goes here...
    end
    

    モジュール化されたスタイルを実現するSinatraアプリケーションには,Sinatra::BaseとSinatra::Applicationの2つの方式を継承し,両者の違いを説明する.
    Sinatra::ApplicationとSinatra::Baseの関係
    通常、モジュール化されたスタイルのSinatraはsinatra::baseを継承しますが、session、flashなどのSinatraフレームワークの内部でデフォルトで実装されている拡張はオフです.デフォルトの拡張を開く最も簡単な方法は、require 'sinatra'が古典的なスタイルのSinatraアプリケーションを実現することです.require 'sinatra'が実現した古典的なスタイルのSinatra応用の原理はSinatra::Applicationを継承し、Sinatra::ApplicationSinatra::Baseを継承した.sinatraソースコードを見るとSinatra::ApplicationSinatra::Baseを継承していることがわかります.
    # https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1902
    class Application < Base
        set :logging, Proc.new { !test? }
        set :method_override, true
        set :run, Proc.new { !test? }
        set :app_file, nil
    
        def self.register(*extensions, &block) #:nodoc:
          added_methods = extensions.flat_map(&:public_instance_methods)
          Delegator.delegate(*added_methods)
          super(*extensions, &block)
        end
      end
    

    Applicationクラスのコメントにこんな一節がありました
    # Execution context for classic style (top-level) applications. All
    # DSL methods executed on main are delegated to this class.
    #
    # The Application class should not be subclassed, unless you want to
    # inherit all settings, routes, handlers, and error pages from the
    # top-level. Subclassing Sinatra::Base is highly recommended for
    # modular applications.
    

    アプリケーションクラスを継承するとSinatraのデフォルトの設定、ルーティングがオンになることを説明するのに十分です.Sinatra::Baseで実装された一連のクラスメソッド(e.g.,get,post,before,configure,set,etc.)
    クラシックスタイルとモジュール化スタイルSinatraアプリケーションの関係
    require 'sinatra'
    
    get '/' do
      ...
    end
    

    コードと組み合わせて、上述した古典的なスタイルを実現するSinatraアプリケーションは、require 'sinatra'であればよい.Sinatraのソースコードを見てみましょう.
    # sinatra/lib/sinatra.rb
    require 'sinatra/main'
    
    enable :inline_templates
    
    # sinatra/lib/sinatra/main.rb
    # https://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb
    require 'sinatra/base'
    
    module Sinatra
      class Application < Base
    
        # we assume that the first file that requires 'sinatra' is the
        # app_file. all other path related options are calculated based
        # on this path by default.
        set :app_file, caller_files.first || $0
        ...
      end
    end
    

    クラシックスタイルのSinatra応用本質も最も基本的なモジュール化スタイルであり、その実現原理はSinatra::Application類を継承している.
    各モジュラースタイルのSinatraはSinatra::Baseを継承する必要があります.Sinatra::ApplicationSinatra::Baseを継承しているので、クラシックスタイルは最も基本的なモジュール化Sinatraアプリケーションです.
    Sinatra拡張
    エレガントなコードは多重化とモジュール化にこだわり,よく使われる機能モジュールを抽象化してSinatra拡張を行う.この章では、Sinatra拡張を実現するために必要な3つの点について引き続き議論します.Sinatra.register、Sinatra.helpers、Module.registeredです.
    拡張は、通常、Sinatra.helpersを用いて実装されるヘルプメソッドモジュール拡張、Sinatra.registerを用いて実装されるルーティングモジュール拡張の2つである.ヘルプメソッド拡張の方法はリクエストコンテキストで使用されます:view,routes,helper、通常は多重化できる論理コードをヘルプメソッドモジュール拡張にカプセル化します.例えば、ページレンダリングヘルプメソッド、ログインするかどうかを判断する論理コードなどです.ルーティング拡張におけるメソッドはSinatra::Applicationのクラスメソッドであり、ある機能特性のルーティングを実現し、通常、ある機能モジュール、例えば、システム登録機能モジュール、インタフェースアクセスチェック機能モジュールをカプセル化するために使用される.
    # lib/sinatra/base.rb
    
    # Extend the top-level DSL with the modules provided.
      def self.register(*extensions, &block)
        Delegator.target.register(*extensions, &block)
      end
    
      # Include the helper modules provided in Sinatra's request context.
      def self.helpers(*extensions, &block)
        Delegator.target.helpers(*extensions, &block)
      end
    

    Extending The DSL (class) Context with Sinatra.register
    Sinatra.registerによって実現される拡張は、Sinatra::Applicationのクラスメソッドを提供する.ルーティングの拡張例:
    require 'sinatra/base'
    module Sinatra
      module LinkBlocker
        def block_links_from(host)
          before {
            halt 403, "Go Away!" if request.referer.match(host)
          }
        end
      end
      register LinkBlocker
    end
    
    Sinatra.registerは、Sinatra::Applicationにクラスメソッドを追加した.古典的なスタイルとモジュール化されたスタイルで私たちのこの拡張を使用すると、次のようになります.
    # classic style
    require 'sinatra'
    require 'sinatra/linkblocker'
    
    block_links_from 'digg.com'
    
    get '/' do
      "Hello World"
    end
    
    # modular style
    require 'sinatra/base'
    require 'sinatra/linkblocker'
    
    class Hello < Sinatra::Base
      register Sinatra::LinkBlocker
    
      block_links_from 'digg.com'
    
      get '/' do
        "Hello World"
      end
    end
    

    ルーティング拡張は、Sinatraアプリケーションによって使用されるために、Sinatraモジュールに登録され、Sinatra.registerによって登録されなければならない.また、クラシックスタイルのsinatraアプリケーションは、最上位レベルのrequireに対応する拡張moduleで直接可能であり、モジュール化スタイルのSinatraアプリケーションは、継承クラス内部でSinatra.registerを介してルーティング拡張を再登録する必要がある.
    Extending The Request Context with Sinatra.helpers
    ヘルプメソッドモジュールは、ルーティング、ビュー、またはヘルプメソッドの追加メソッドに拡張されます.次の例では,hヘルプメソッドというメソッドを実現する.
    require 'sinatra/base'
    
    module Sinatra
      module HTMLEscapeHelper
        def h(text)
          Rack::Utils.escape_html(text)
        end
      end
    
      helpers HTMLEscapeHelper
    end
    

    上記の例では、helpers HTMLEscapeHelperメソッドは、拡張で定義されたすべてのモジュールメソッドをSinatra::Applicationに追加した.これらの方法は古典的なスタイルで使用できます
    require 'sinatra'
    require 'sinatra/htmlescape'
    
    get "/hello" do
      h "1 < 2"     # => "1 < 2"
    end
    

    クラシックスタイルで拡張方法を引用するには、require 'sinatra/htmlescape'だけです.
    しかし、sinatra.helpersで実装された拡張メソッドをモジュール化スタイルで使用するには、最上位レベルのrequireに対応する拡張モジュールのほかに、helpersメソッドを使用して導入する必要があります.
    require 'sinatra/base'
    require 'sinatra/htmlescape'
    
    class HelloApp < Sinatra::Base
      helpers Sinatra::HTMLEscapeHelper
    
      get "/hello" do
        h "1 < 2"
      end
    end
    

    registeredモジュールメソッドを使用してルーティング拡張を構成する
    モジュール内でregisteredモジュールメソッドを実装することによって、ルーティング拡張のオプション、ルーティング、フィルタ、および例外処理を設定することができる.モジュールが参照されると、拡張モジュールに定義されたregisteredメソッドがSinatra::Baseモジュールに追加されます.registeredメソッドにはappパラメータがあり、ルーティング拡張は登録が実装された後(ルーティング拡張に反映されるregister ExtensionsName)、Sinatraは現在のアプリケーションのコンテキストをregisteredメソッド(すなわちデフォルトのappパラメータ)に渡し、appパラメータによってSinatra::Baseで定義されたDSLメソッドを呼び出すことができる.
    簡単なログイン検証拡張例を実現
    # lib/sinatra/auth.rb     
    require 'sinatra/base' #         require 'sinatra/base'
    require 'sinatra/flash'
    
    module Sinatra
      module Auth
        module Helpers
          def authorized?
            session[:admin]
          end
    
          def protected!
            halt 401,slim(:unauthorized) unless authorized?
          end
        end
    
        def self.registered(app)
          app.helpers Helpers #         
    
          app.enable :sessions #    Sinatra session
          app.set :username => 'frank', :password => 'sinatra' #      
    
          #    ```Sinatra::Base     DSL   
          app.get '/login' do
            slim :login
          end
    
          app.post '/login' do
            if params[:username] == settings.username && params[:password] == settings.password
              session[:admin] = true
              flash[:notice] = "You are now logged in as #{settings.username}"
              redirect to('/')
            else
              flash[:notice] = "The username or password you entered are incorrect"
              redirect to('/login')
            end
          end
    
          app.get '/logout' do
            session[:admin] = nil
            flash[:notice] = "You have now logged out"
            redirect to('/')
          end
        end
      end
    
      register Auth #        
    
    end
    
    # config/application.rb     
    require 'sinatra/base'
    require_relative '../sinatra/auth'
    
    class Application < Sinatra::Base
      set :root, File.dirname(__FILE__) #        
      set :views, "#{settings.root}/../app/views" #           
      set :public_folder, 'public'  #         
    
      register Sinatra::Auth #        Sinatra      Auth     
    end
    
    #       
    #   Auth                  Helpers        :
    #         authorized?   protected!
    #                    、  、   。
    #     :      ,              
    footer
      - if authorized?
        a href="/logout" log out
      - else
        a href="/login" log in
    
    

    最後に書く
    Railsフレームワークを使いたくないRuby開発者にとって、Sinatraは軽量でプロジェクトを迅速に開発できる良い代替フレームワークです.筆者はこれを用いて,APIと各種H 5フロントエンドページのレンダリングを提供する支払いチャネル中台プロジェクトを開発した.この文章は3ヶ月前から書き始めましたが、遅延と惰性の断続的な続編のため、書くたびに以前所蔵していた文章をもう一度見て考えを整理しなければなりません.異常に時間と苦痛を浪費しています.自分に一つの説明をして、一つのプロジェクトをまとめて反省しなければ、間もなく忘れてしまうと自分に言った.また最近はGoを勉強しているので、これからもっとRuby開発を使うことになると思います.
    リンクを参照して読む価値のある記事
  • writing sinatra extensionsはSinatra拡張の素晴らしい文章を書くことを教えてくれました.まず,Sinatra::BaseSinatra::Applicationの役割と両者の相違を解析した.次に、Sinatra拡張の4つのルールについて説明します.次に、Sinatra.helpersを使用してSinatraリクエストコンテキスト(コントローラ、ビュー、ヘルプメソッドで使用できる方法をカスタマイズする)を追加し、Sinatra.registerを使用してDSLコンテキストを追加する方法について説明します.(本質的にはSinatra::Applicationにクラスメソッドを追加します.シーンは通常Railsのxx_actionのように使用されます)最後に、module.registeredを介して拡張設定(設定、フィルタ、コントローラの例外処理を定義する方法を含む)
  • を記述する方法について説明します.
  • Sinatra Best Practices:Part Two sinatraのrspec
  • の構成方法
  • プロジェクトプロファイル
  • について
  • プロジェクトファイルロード
  • について
  • プロジェクトディレクトリ構造組織
  • について
  • Sinatraでrakeとactive record
  • を使用
  • プロジェクトファイル構造構築詳細ステップ
  • Sinatra applications with RSpec
  • ネーミングスペース
  • 完全Sinatraプロジェクトdemo