社内の認証基盤で、Active Directory(AD) を使っている企業は多くありますが、
業務システムの認証基盤として、 社内の AD を使用したいというニーズもあると思います。
今回は、Rails で業務システムを作り、Devise を使用して、AD 認証を実装してみます。
Ruby と Rails のバージョンは↓のとおりです。
$ ruby -v
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-linux]
$ bundle exec rails -v
Rails 5.2.3
Gemfile の編集
AD 認証を実装するために、Devise と、Devise LDAP Authenticatable(Devise LDAP) を使用します。
ちなみに、Devise の詳しい解説については、よくわかってないのでこの記事では取り扱いませんのであしからず。
gem 'devise'
gem 'devise_ldap_authenticatable'
編集したら、bundle install
でインストールします。
Devise と Devise LDAP のセットアップ
はじめに、Devise をセットアップしUser
モデルを作成します。その後で、Devise LDAP のセットアップを行います。
bundle exec rails g devise:install ## Devise のインストール
bundle exec rails g devise User ## User モデルの作成
bundle exec rails g devise_ldap_authenticatable:install ## Devise LDAP のインストール
User モデルの編集
生成された User モデルを編集します。
トラブルシュートなどでの利用を想定して、ログインしたPCのIPアドレスを記録したり、
ログイン回数などを記録できる:trackable
を追記しました。
# app/models/user.rb
class User < ApplicationRecord
devise :ldap_authenticatable, :rememberable, :trackable
end
マイグレーションファイルの修正
修正した User モデルに合わせて、マイグレーションファイルを修正します。
また、デフォルトではメールアドレスとパスワードで認証を行うのですが、メールアドレスを持っていなかったり、AD の mail 属性を使っていない場合を考慮して、ログインユーザー名(sAMAccountName)でログインできるようにします。
# db/migrate/XXXXXXXXXX_devise_create_users.rb
# frozen_string_literal: true
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
## LDAP authenticatable
t.string :username, null: false, default: "", unique: true
## Database authenticatable
# t.string :email, null: false, default: ""
# t.string :encrypted_password, null: false, default: ""
## Recoverable
# t.string :reset_password_token
# t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :username, unique: true
# add_index :users, :email, unique: true
# add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
マイグレーションファイルを修正したら、bundle exec rails db:migrate
でマイグレートします。
Devise の初期設定
Devise の初期設定を行います。変更した箇所だけを抜粋します。
今回は、アプリ側でユーザー登録しなくても、AD にユーザーがいればログイン可能なようにしたいと思います。
# config/initializers/devise.rb
config.ldap_logger = true # LDAP クエリを Rails のログに記録する。
config.ldap_create_user = true # すべての有効な AD ユーザーがログイン可能になり、自動的にユーザーのレコードが登録される。
config.ldap_update_password = false # パスワード変更を AD 側に書き戻さないようにする。
config.ldap_use_admin_to_bind = true # LDAP認証時のバインドで Administrator を利用する。
config.authentication_keys = [:username] # 認証で利用するキー。今回は、User モデルの username を利用する。
config.case_insensitive_keys = [:username] # キーの大文字小文字を区別しない
config.strip_whitespace_keys = [:username] # キーに含まれる空白を削除する
LDAP の設定
AD の接続情報などを設定します。
# config/ldap.yml
development:
host: <ドメインコントローラ名>
port: 389
attribute: sAMAccountName
base: CN=Users,DC=devel,DC=local
admin_user: CN=Administrator,CN=Users,DC=devel,DC=local
admin_password: <admin_user のパスワード>
ssl: false
View の作成
Devise の View を作成します。今回は、ユーザー登録やメールでの登録確認等は行わないので、ログイン画面だけを作成します。
bundle exec rails generate devise:views users -v sessions
View の修正
作成したログイン画面を修正します。
デフォルトではメールアドレスの入力欄になっているので、これを Windows アカウント名の入力欄に変更します。
# app/views/users/sessions/new.html.erb
<h2>Log in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="field">
<%= f.label :username, "Windows アカウント名" %><br />
<%= f.text_field :username, autofocus: true, autocomplete: "username" %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "current-password" %>
</div>
<% if devise_mapping.rememberable? %>
<div class="field">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<% end %>
<div class="actions">
<%= f.submit "Log in" %>
</div>
<% end %>
<%= render "users/shared/links" %>
Controller の生成
次にコントローラを作成します。
ログイン周りのハンドリングができれば良いので、Sessions のコントローラのみを生成します。
bundle exec rails generate devise:controllers users -c=sessions
Controller の修正
生成したコントローラはすべてコメントアウトされているので、適宜コメントを外します。
# app/controllers/users/sessions_controller.rb
# frozen_string_literal: true
class Users::SessionsController < Devise::SessionsController
before_action :configure_sign_in_params, only: [:create]
# GET /resource/sign_in
def new
super
end
# POST /resource/sign_in
def create
super
end
# DELETE /resource/sign_out
def destroy
super
end
protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_in_params
devise_parameter_sanitizer.permit(:sign_in, keys: [:username])
end
end
ログイン後のページを作成
今の状態だと、ログイン画面はありますがログイン後に表示するページがありません。
静的なページhome
を作って、ログイン後はhome
に遷移するようにします。
bundle exec rails g controller StaticPages home
デフォルトで生成されるページだとあまり面白くないので、username を表示させたいと思います。
また、どこからもログアウトできないと困るので、ログアウトのリンクを追記します。
# app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>
<p>Welcome <%= current_user.username %> !</p>
<%= link_to "ログアウト", destroy_user_session_path %>
routes の編集
コントローラを作成した際にいくつか自動で追加されているルートもありますが、今回のアプリではログイン画面を URL の ルートにしたいと思います。
こんな感じで routes.rb を編集します。
# config/routes.rb
Rails.application.routes.draw do
get 'home', to: 'static_pages#home'
devise_for :users, controllers: {
sessions: 'users/sessions'
}
devise_scope :user do
root to: 'users/sessions#new'
get 'sign_in', to: 'users/sessions#new'
get '/users/sign_out', to: 'users/sessions#destroy'
end
end
デフォルトの動作では、ログインしたあとに URL のルートに遷移するため、ログイン画面が無限ループします。
それを回避するために、application_controller.rb
に、after_sign_in_path_for
メソッドを追記してhome
に遷移させます。
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
rescue_from DeviseLdapAuthenticatable::LdapException do |exception|
render :text => exception, :status => 500
end
def after_sign_in_path_for(resource)
home_path
end
end
ログインしてみる
ここまでできたらbundle exec rails s
でサーバーを起動して、実際にログインしてみましょう。localhost:3000
にアクセスしたら、こんなログイン画面が表示されるはずです。
AD に登録されているユーザー名とパスワードを入力してログインすると、、、
アプリ側で特にユーザー登録をしていませんが、ADで認証されたユーザーが Rails のデータベースに登録されているので、無事にログインできました。
ところで、ひとつバグがあります。
今の状態だとログインしていなくてもhome
にアクセスできてしまうので、home
は認証必須にします。
before_action
フィルタで、認証済みのユーザーのみがhome
にアクセスできるようにします。
# app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
before_action :authenticate_user!
def home
end
end
これで、ダイレクトに/homeにアクセスしようとすると、ログイン画面にリダイレクトされるようになりました。
今回作成したアプリのリポジトリはGithubに登録しています。
ad_auth
続編を書きました!!
Devise を使って Active Directory から情報を取得する方法