Sorcery와 비교해서(지난번에 살펴본), Devise는 당신을 위해 많은 다양한 관점을 다루는 고수준의 솔루션이다. Devise는 controllers, views, mailers 그리고 routes를 제공한다. 실행하는 몇몇 커맨드를 입력하는 것은 쉬운반면 매우 세밀하게 커스터마이징할 수 있다. Devise는 매우 완전한 문서와 많은 유용한 확장기능을 만들어내는 큰 커뮤니티를 가지고 있다. Devise가 매우 인기가 많은 것은 경탄할만한 것은 아니다.
Devise는 필요한 것들만 고를 수 있는 편리한 모듈들을 가지고 있다. password recovery, e-mail confirmation account lock out, 그리고 매우 많은 다른 모듈들을 지원한다.
이 문서에서는 다음의 것들을 다룰 것이다:
- 데모 앱에 Devise 통합하기
- Devise를 셋업하고 특정 모듈을 enable 시키기
- Devise를 커스터마이징하기
- 특정 페이지에 대한 액세스를 제한하기
- 비동기 e-mail delivery를 셋팅하기
- password strength를 평가하는 Devise 확장기능을 통합하기
- password strength 평가를 client-side에 추가하기
좋아보이는가? 시작해보자!
데모 애플리케이션은 https://sitepoint-devise.herokuapp.com/ 에 있다.
소스 코드는 Github에 있다.
Ground Work
시리즈의 이전 문서에서 보았듯이 우리는 인증외에는 아무런 기능을 제공하지 않는 앱을 생성할 것이다(몇몇 관련된 특성들을 가진다). 나는 지금 어떤 화려한 이름도 생각해낼 수 없다. 그래서 그냥 "Devise Demo"라고 부르겠다.
$ rails new DeviseDemo -T
Rails 4.20이 사용되었다. 그러나 Devise는 Rails 3과도 잘 호환된다.
몇몇 gem들을 설치하자:
Gemfile
[...] gem 'devise', '3.4.1' gem 'bootstrap-sass' [...]
bootstrap-sass는 튜토리얼과 연관된 것은 아니지만 스타일링때문에 좋아한다.
다음을 실행한다.
$ bundle install
Bootstrap 스타일을 연결한다.
application.scss
@import "bootstrap-sprockets"; @import "bootstrap"; @import 'bootstrap/theme';
그리고 레이아웃을 변경한다:
views/layout/application.html.erb
[...] <div class="container"> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> </div> <%= yield :top_content %> <div class="container"> <h1><%= yield :header %></h1> <%= yield %> </div> [...]
어떤 방식으로든 flash 렌더링 기능을 추가해야 하는 것을 주의해라. Devise는 다양한 메시지를 표시하기 위해 flash에 의존한다. Devise와 관련된 bundle을 처음 실행시켰을 때 그것과 관련된 메시지를 보게될 확률이 크다.
보시다시피, top-content와 header라는 추가적인 컨텐츠를 위치시키기 위해 yield를 사용하고 있다. 또한 헤더를 제공하기 위해 헬퍼 메소드를 추가했다.
application_helper.rb
[...] def header(text) content_for(:header) { text.to_s } end [...]
또한 우리의 홈페이지를 셋업하기 위해 PagesController를 생성하자.
pages_controller.rb
class PagesController < ApplicationController end
그리고 나서 view는:
views/pages/index.html.erb
<% content_for :top_content do %> <div class="jumbotron"> <div class="container"> <h1>Welcome!</h1> <p>Register to get started.</p> <p> <%= link_to 'Register', new_user_registration_path, class: 'btn btn-primary btn-lg' %> </p> </div> </div> <% end %>
그리고 이들을 모두 묶기 위해 route를 수정한다.
config/routes.rb
[...] root to: 'pages#index' [...]
마지막으로, 개발을 위해 기본 URL 옵션을 설정하자.
config/environments/development.rb
[...] config.action_mailer.default_url_options = { host: '127.0.0.1', port: 3000 } [...]
이는 e-mail 뷰 내부에 적절한 링크를 생성하기 위해 필요하다.
이제, Devise를 통합할 눈부신 시간이다.
Integrating Devise
우선, Devise의 configuration 파일과 transaction을 생성하기 위해 다음 명령을 실행하자:$ rails generate devise:install
config/initializers/devise.rb 는 문서화가 잘된 많은 다양한 설정 옵션들을 포함하고 있다. 우리는 잠깐 이 파일을 수정할 것이다. config/locales/devise.en.yml 은 영어로 Device의 특정 번역본을 제공한다. 여기에 많은 다른 언어를 위한 번역본이 있다.
다음은 Device가 필요로 하는 추가적인 컬럼을 가진 모델을 생성한다.
$ rails generate devise User
당신은 User를 다른 어떤 이름으로로도 변경할 수 있다. 이 명령은 user.rb 모델 파일을 생성하고 모든 필요한 필드를 추가하는 마이그레이션을 생성한다. 만약 User 모델이 이미 존재하면, 업데이트 될 것이다.
모델 파일이 무엇을 포함하고 있는지 열어보자. 가장 중요한 라인은:
models/user.rb
[...] devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable [...]
이것은 이 모듈을 위해 활성화된 Divise 모듈의 리스트이다.
- datebase_authenticatable - 사용자들은 데이터베이스에 저장된 로긴과 패스워드로 인증할 수 있을 것이다. (password is stored in a form of digest).
- registerable - 사용자들은 그들의 프로파일을 등록하고 업데이트하고 제거할 수 있다.
- recoverable - 잃어버린 패스워드를 리셋하는 메커니즘을 제공한다.
- rememberable - 쿠키와 연관된 "remember me" 기능을 사용가능하다.
- trackable - count, timestamps, IP 주소를 추적한다.
- validatable - e-mail 과 password를 인증한다(custom validator도 사용가능하다)
보이는가? Devise는 당신을 위해 매우 다양한 측면들을 다룬다. - 단지 필요한 모듈을 골기만 하면 된다.
이 데모를 위해 두개의 추가적인 모듈을 사용해보자:
- confirmable - 로그인이 허가되기 전에 등록 후 사용자의 e-mail 승인을 필요로 한다.
- lockable - 사용자의 계정은 몇번의 인증 시도 실패후 잠길 것이다.
모델을 아래와 같이 수정해보자.
user.rb
[...] devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable [...]
당신은 또한 마이그레이션 파일을 수정해야 한다. 파일을 열어서 다음의 라인을 주석해제하자.
db/migrate/xxx_devise_create_users.rb
[...] t.string :confirmation_token t.datetime :confirmed_at t.datetime :confirmation_sent_at t.string :unconfirmed_email t.integer :failed_attempts, default: 0, null: false t.string :unlock_token t.datetime :locked_at add_index :users, :confirmation_token, unique: true add_index :users, :unlock_token, unique: true [...]
이들 필드들은 Confirmable 과 Lockable 모듈들이 정확하게 동작하게 하는데 필요하다. 또한 사용자가 그들의 이름을 제공하도록 하는 것도 좋은 아이디어이며 그래서 한 라인을 더 추가한다:
db/migrate/xxx_devise_create_users.rb
[...] t.string :name [...]
이제, 마이그레이션을 실행해보자:
$ rake db:migrate
users 테이블이 이제 생성되었고 Devise가 up 되고 실행되고 있다. 그러나 여전히 몇몇 해야할 일이 남아있다.
Setting Up Devise
이전에 언급하였듯이, Devise 셋팅들은 devise.rb initializer 파일 내에 제시되어 있다. 파일을 열어서 "Configuation for :lockable" 섹션을 찾아보자. 여기 대부분의 셋팅들은 코멘트 처리되어 있을 것이다. 그래서 그것들을 코멘트 해제하고 다음 값들을 제공하자:- config.lock_strategy = :failed_attempts - 이것은 몇번의 실패한 로긴 시도 이후에 계정이 잠길 것이라는 것을 의미한다. 실제로, 이는 유일한 사용가능한 전략이다. 그러나 당신은 이 셋팅을 none으로 셋팅하고 locking 메커니즘을 스스로 처리할 수 있다.
- config.unlock_strategy = :both - 계정은 e-mail(Devise가 보낸 이메일에서 제공한 링크를 방문함으로써)을 통해서나 특정 시간을 기다림으로써 잠금해제가 될 수 있을 것이다. 이 두 방법 중 오직 하나만 사용가능하게 할려면 :email 이나 :time을 제공해라. 잠금해제를 스스로 처리하고자 하면 :none을 제공하여라.
- config.maximum_attempts = 20 - 사용자가 계정이 잠금처리되기 전에 부정확한 패스워드를 입력할 수 있는 연속적인 횟수
- config.unlock_in = 1.hour - 계정은 얼마 뒤에 잠금해제될 수 있는가. 이 셋팅은 오직 :time 이나 :both 잠금해제 전략을 사용한다면 제공한다.
- config.last_attempt_warning = true - 사용자가 한번 로긴 시도를 남겼을 때 경고를 보여준다. 이는 경고가 flash 메시지 형태로 보여질 것이다.
좋다. 이제 "Configuration for : confirmable" 섹션을 살펴보고 이들 세팅을 잠금해제 하자.
- config.confirm_within = 3.days - 사용자가 e-mail에 보내진 링크를 통해 계정을 활성화해야하는 시간이다(기본적으로 이는 Devise에 의해 생성된 활성화 토큰이 더이상 유효하지 않다는 것을 의미한다). 만약 계정이 활성화되지 않으면, 새로운 토큰이 요청될 수 있다. 만약 활성화 토큰이 기간만료되는 것을 원하지 않으면 이 값을 nil로 셋팅해라.
- config.reconfirmable = true - 사용자는 프로파일 업데이트를 통해 e-mail을 변경할 때 재확인을 해야 한다. 이 절차는 등록 이후 확인 절차와 동일하다. 이 새로운, 미인증 e-mail은 사용자가 활성화 링크를 방문하기 전까지는 unconfirmed_email 필드에 저장될 것이다. 이 기간동안 로긴하는데 이전 e-mail이 사용된다.
만약 당신이 실제 애플리케이션을 만들고 있다면, 이들 셋팅값을 조정하는 것을 잊지마라.
- config.mailer_sender - "From" 필드에 삽입될 e-mail을 여기에 제공하라.
- config.secret_key - 다양한 토큰을 생성할 Secret key. 이 값을 변경하는 것은 이전에 생성된 모든 토큰을 무효화할 것이다.
이 파일에는 당신이 변경하기 원하는 더 많은 셋팅이 존재한다. 예를 들어, config.password_length는 최소, 최대 패스워드 길이를 셋팅한다.(기본으로 8에서 128이다).
Customizing Devise
Generating Views
이제 Devise는 우리가 좋아하는 방식으로 셋업되었다. 서버를 부팅하고 메인페이지를 네비게이팅한다. 그리고 "Register" 링크를 클릭한다. 당신에게 기본 폼이 제공될 것이다. 그러나 주의해야할 두가지가 있다:
- 이 폼은 당신에게 이름을 입력할 폼을 제공하지 않는다. 우리가 마이그레이션 파일에 추가하였음에도 불구하고
- 만약 당신이 스타일링을 위해 Bootstrap을 사용한다면, 폼은 좋게 보이지 않을 것이다(좋다. 이것은 큰 문제가 아니다. 그러나 이를 지속적으로 고려해보자.
다행히도, Devise에 의해 제공되는 모든 뷰들을 커스터마이징 할 수 있는 방법이 있다. 다음을 실행해보자.
이 명령은 기본 Devise 뷰들을 바로 당신의 애플리케이션 폴더로 복사한다. devise 라고 불리는 새로운 폴더는 views 디렉토리에 생성될 것이다. 간략히 내부의 모든 폴더를 살펴보도록 하자.
심지어 다른 모델들을 위한 분리된 뷰를 생성할 수 있다(만약 Devise를 갖춘 당신의 앱에서 다수의 모델이 있다면)
이부분을 더 읽어라
views/devise/registrations/new.html.erb
header 는 페이지의 헤더를 디스플레이하는 helper 메소드이다. divise_error_messages! 는 레코드를 저장하는 동안 발생하는 에러들을 렌더링한다.
서버를 재시작하고 새로운 사용자를 등록하자. 이제 사용자 이름을 제공할 수 있을 것이다.
user_signed_in? 은 사용자가 로그인했는지 여부를 알려주는 Devise의 헬퍼 메소드이다. current_user는 user record를 리턴하거나 사용자가 로그인하지 않았다면 nil을 리턴한다. 만약 Devise 마이그레이션을 생성하는 동안 모델명을 다르게 했다면 이들 헬퍼 메소드들 또한 다른 이름을 가지고 있을 것이다. Admin 모델의 경우, admin_signed_in? 그리고 current_admin이 될 것이다. 메타프로그래밍은 좋다.
모든 routes(물론 root_path와 더불어)들은 Devise에 의해 잘 제공된다. destroy_user_session_path (log out)은 기본으로 DELETE HTTP 메소드를 필요로한다는 것에 주의해라. 만약 그것이 싫다면, devise.rb initializer 안에 있는 config.sign_out_via 를 변경하라.
스타일링 목적으로 Bootstrap's 의 dropdown 메뉴를 사용할 것이다. 만약 그것을 잘 사용하려면, 몇몇 action들이 더 필요할 것이다. Dropdown은 JavaScript 코드에 의지하고 Turbolinks를 사용한다. 페이지 사이를 이동할 때 turbolinks가 정확하게 동작하지 않을 거서이다. jquery_turbolinks를 사용해 그것을 수정해라.
Gemfile
이제 application.js 파일을 수정해보자.
application.js
Devise 의 특정 flash message를 스타일링하기 위해 Sass @extend 메소드를 사용하자(이 메소드를 남용하지 말자. 이 지시자는 몇몇 결점이 있다):
application.scss
서버를 다시 읽어들이고 drop down 메뉴를 살펴보자. 왜 사용자이름이 표시되지 않는가?
:name 속성이 화이트 리스팅 되어야한다.
application_controller.rb
우리는 모든 Devise controllers에 sign up과 account update 액션을 위한 :name 속성을 화이트리스팅하는 before_action을 추가하였다. 만약 중첩된 해쉬 또는 배열을 가진 약간 더 복잡한 시나리오를 가지고 있다면 문서의 이 부분을 참고하여라.
당신의 이름을 변경하기 위한 프로파일 페이지를 이끌어가기 전에 대응되는 뷰가 업데이트 되어야 한다:
views/devise/registrations/edit.html.erb
사용자는 또한 그들의 이메일 주소, 비밀번호 또한 변경하길 원할 것이다(우리가 이전에 논의했듯이 확인이 필요하다) 그리고 심지어 프로파일을 완전히 삭제하길 원할 것이다.
다른 Devise의 뷰들은 어떻게 스타일링하는지는 보여주지 않을 거서이다. 관심이 있다면 GitHub repo 를 확인하라.
Sending E-mail and DelayedJob
이제 간단하게 이메일을 보내는 절차에 대해 논의해보자. 먼저 개발 환경에서 이메일은 기본으로 보내지지 않는다는 것을 기억해야 한다. 여전히 콘솔을 통해 코멘트처리 되어 있는 것을 볼 수 있을 것이다. enable 시켜보자.
config/environments/development.rb
그리고 ActionMailer를 설정하자(여기 예제가 있다)
다음으로 production을 위해 유사한 설정을 추가해야할 것이다. 여기 데모 앱을 위한 설정이 있다.
config/environments/production.rb
이 앱은 Heroku에서 실행되기 때문에 나는 그것의 메일 전송 add-ons 중 하나를 사용해야 했다(당신은 이메일을 곧바로 보낼 수 없을 것이다). 나의 설정에서 Sendgrid 가 사용된다. 이것은 free plan 상에서 실행되기 때문에 메일이 전송되는데 시간이 걸릴 것이다.
어째든, 당신은 커스텀 메일을 사용하기 원한다면, devise.rb에 있는 config.mailer를 수정하여라.
그리고 마지막으로 이메일을 보내기를 백그라운드로 실행하는 것이 좋은 아이디어이다. 반면에 사용자들은 새로운 페이지로 리다이렉트 되기 전에 메일이 보내질때까지 기다려야 할 것이다. Devise에 Delayedjob을 예제와 같이 통합해 보자.
새로운 gem을 추가하자:
Gemfile
그리고 실행하자.
$ bundle install
$ rails generate delayed_job:active_record
$ rake db:migrate
gem 을 설치하고 필요한 마이그레이션을 생성하고 적용하기(생성될 scheduled task들을 저장할 새로운 테이블)
Rails4.2의 경우 queueing backend를 셋팅하기 위해 application.rb 파일을 변경해라
config/application.rb
이제, 당신 모델의 Devise의 메소드를 오버라이딩하자.
models/user.rb
deliver_later 는 전송이 큐에 담길 것을 의미한다.
만약 앱을 Heroku에 배포하고자한다면, 당신 프로젝트 루트에 Profile을 생성하고 다음 라인을 추가하라.
Profile
worker: rake jobs:work
job을 처리하기 위해 적어도 하나의 worker 프로세스가 job을 처리하도록 해야할 것이고 그것은 한달에 $35의 경비가 소요될 것이다.
$ heroku ps:scale worker=1
Heroku에서 Delayedjob을 사용하는 것에 대한 매우 이해하기 쉬운 가이드가 있다. 더 자세한 사항은 그것을 참고하라.
데모 앱에서는 background sending을 enable 하지 않을 것이다. 그러나 관련 코드는 GitHub repo에 있다.
Devise는 그것 또한 잘 다루는 것으로 보인다: 당신은 대응되는 메소드를 before_action 처럼사용하기만 하면 된다. 먼저 우리는 제한될 특별한 페이지가 필요하다. 그것을 간단히 "Secret" 이라고 부르자.
config/routes.rb
의미없는 간단한 뷰는 :
views/pages/secret.html.erb
당신은 심지어 secret 메소드를 PagesController에 추가할 필요조차 없다 - Rails는 그것을
암암리에 정의할 것이다.
메뉴를 약간 수정해보자:
views/layouts/application.html.erb
그리고 controller를 수정해보자.
before_action은 secret 메소드를 호출하기 전에 사용자가 인증된 사용자인지 체크할 것이다. 미인증 사용자는 "Please authenticate" flash 메시지를 가진 페이지로 리다이렉트될 것이다. 이름이 다른 모델의 경우 이 메소드는 다른 이름을 가질 것이라는 것을 다시 한번 상기하자(Admin 모델의 경우 authenticate_admin!이다).
이 extension은 페스워드 길이를 측정하고 약한 패스워드는 거부한다. 그것은 zxcvbn-ruby에 의존하는데 Dropbox에 의해 만들어진 zxcvbn.js의 루비 포팅버전이다. 어떻게 이 솔루션이 동작하는지 알고 싶으면 이 공식 블로그 포스트를 읽어라.
새 gem을 추가하자:
Gemfile
그리고 다음을 실행한다.
$ bundle install
그리고 당신의 모델을 위한 새로운 모듈을 등록하자.
models/user.rb
이제 User는 zxcvbnable 하다(그냥 발음해보아라). 이 extension을 위한 하나의 셋팅만 존재한다.
config/initializers/devise.rb
이는 기본적으로 패스워드가 얼마나 강력해야하는가를 의미한다(높은 수가 더 강력하다). Strength는 cracking time을 측정한것으로 이해하면 된다.
사용자는 복잡한 패스워드를 생각할 필요가 없을 것이므로 여기서는 0으로 셋팅했다. 실제 앱에서는 그러한 약한 패스워드를 허락하는 것은 추천하지 않는다.
마지막으로 에러 메시지를 커스터마이징하자:
config/locales/devise.en.yml
다양한 패스워드를 시도해보고 얼마다 그들이 강력한지 체크해보자.
global.coffee
그리고 연결해보자.
application.js
score 는 0에서 4까지의 수를 리턴 한다 - 우리는 조금 전에 그 값들에 대해 얘기했었다 - 그리고 그것은 strength를 측정하는 가장 편리한 방법으로 보인다. crack_time_display는 친근한 방식으로 패스워드를 크랙하는데 필요한 시간 측정값을 리턴한다("3days", "4days", "centuries" 등등).https://github.com/plataformatec/devise/wiki/How-Tos
이제 estimate-password 클래스를 원하는 아무 패스워드 필드에 할당해보자. 예를 들어
views/devise/registrations/new.html.erb
물론, 이것은 가장 간단한 솔루션이다. 그러므로 이것을 자유롭게 확장해라.
난 Rails 시리즈와 함께한 Authentication 의 두번째 파트를 당신들이 즐겼기를 바란다. 다음 문서에서 우리는 Oauth2와 함께한 Authentication에 대해 논의할 것이다.
출처 http://www.sitepoint.com/devise-authentication-in-depth/
$ rails generate devise:views
이 명령은 기본 Devise 뷰들을 바로 당신의 애플리케이션 폴더로 복사한다. devise 라고 불리는 새로운 폴더는 views 디렉토리에 생성될 것이다. 간략히 내부의 모든 폴더를 살펴보도록 하자.
- confirmations - 이 디렉토리는 사용자가 confirmation e-mail을 다시 보내도록 요청할 때 그려지는 하나의 new.html.erb 뷰를 가지고 있다.
- mailer - 이메일을 위한 모든 템플릿이 여기 저장되어 있다.
- passwords - 패스워드를 요청하기 위한 폼을 가진 뷰를 가진다. 이메일을 리셋하고 실제로 패스워드를 변경한다.
- registrations - 사이트에서 사용자가 등록할 때 new.html.erb 뷰가 렌더링된다. edit.html.erb는 프로파일을 업데이트하는 폼을 포함한다.
- sessions - 사이트를 위한 로긴 폼인 하나의 뷰를 가진다.
- shared - 하나의 partial만이 제공된다. 이는 각 Devise의 페이지상에서 표시될 링크들을 포함한다("Forgot your password?" "Re-send confirmation email" 등과 같은)
- unlocks - unlock link를 가진 email을 요청하는 폼을 가진 하나의 뷰를 가진다.
만약 특정 모듈만을 위한 뷰를 커스터마이징하고자 한다면(예를 들어, registerable 과 confirmable) 이 명령을 실행해라:
$ rails generate devise:views -v registrations confirmations
심지어 다른 모델들을 위한 분리된 뷰를 생성할 수 있다(만약 Devise를 갖춘 당신의 앱에서 다수의 모델이 있다면)
$ rails generate devise:views users
이부분을 더 읽어라
Registration Form
이제 하나 이상의 필드를 추가하고 스타일림함으로써 등록 폼을 제공하는 뷰를 변경하자.views/devise/registrations/new.html.erb
<% header "Sign Up" %> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= devise_error_messages! %> <div class="form-group"> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, autocomplete: "off", class: 'form-control' %> <% if @validatable %> <span class="help-block"><%= @minimum_password_length %> characters minimum</span> <% end %> </div> <div class="form-group"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %> </div> <%= f.submit "Sign up", class: 'btn btn-primary' %> <% end %> <%= render "devise/shared/links" %>
header 는 페이지의 헤더를 디스플레이하는 helper 메소드이다. divise_error_messages! 는 레코드를 저장하는 동안 발생하는 에러들을 렌더링한다.
서버를 재시작하고 새로운 사용자를 등록하자. 이제 사용자 이름을 제공할 수 있을 것이다.
Main Menu and Flash Messages
main page로 리다이렉트된 후, 몇몇 이슈들을 확인하게 될 것이다.- 현재 어떤 사용자가 로그인했는지 확인할 방법이 없다.
- 로그아웃할 방법이 없다.
- welcome flash message는 정확하게 스타일되지 않았다(만약 Bootstrap이 함께 한다면)
첫 두 이슈는 아래와 같이 메인 메뉴를 추가함으로써 쉽게 해결된다.
views/layouts/application.html.erb
<nav class="navbar navbar-inverse"> <div class="container"> <div class="navbar-header"> <%= link_to 'Devise Demo', root_path, class: 'navbar-brand' %> </div> <div id="navbar"> <ul class="nav navbar-nav"> <li><%= link_to 'Home', root_path %></li> </ul> <ul class="nav navbar-nav pull-right"> <% if user_signed_in? %> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#"> <%= current_user.name %> <span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> <li><%= link_to 'Profile', edit_user_registration_path %></li> <li><%= link_to 'Log out', destroy_user_session_path, method: :delete %></li> </ul> </li> <% else %> <li><%= link_to 'Log In', new_user_session_path %></li> <li><%= link_to 'Sign Up', new_user_registration_path %></li> <% end %> </ul> </div> </div> </nav>
user_signed_in? 은 사용자가 로그인했는지 여부를 알려주는 Devise의 헬퍼 메소드이다. current_user는 user record를 리턴하거나 사용자가 로그인하지 않았다면 nil을 리턴한다. 만약 Devise 마이그레이션을 생성하는 동안 모델명을 다르게 했다면 이들 헬퍼 메소드들 또한 다른 이름을 가지고 있을 것이다. Admin 모델의 경우, admin_signed_in? 그리고 current_admin이 될 것이다. 메타프로그래밍은 좋다.
모든 routes(물론 root_path와 더불어)들은 Devise에 의해 잘 제공된다. destroy_user_session_path (log out)은 기본으로 DELETE HTTP 메소드를 필요로한다는 것에 주의해라. 만약 그것이 싫다면, devise.rb initializer 안에 있는 config.sign_out_via 를 변경하라.
스타일링 목적으로 Bootstrap's 의 dropdown 메뉴를 사용할 것이다. 만약 그것을 잘 사용하려면, 몇몇 action들이 더 필요할 것이다. Dropdown은 JavaScript 코드에 의지하고 Turbolinks를 사용한다. 페이지 사이를 이동할 때 turbolinks가 정확하게 동작하지 않을 거서이다. jquery_turbolinks를 사용해 그것을 수정해라.
Gemfile
[...] gem 'jquery-turbolinks' [...]다음을 실행하는 것을 잊지마라.
$ bundle install
이제 application.js 파일을 수정해보자.
application.js
[...] //= require jquery //= require jquery.turbolinks //= require jquery_ujs //= require bootstrap/dropdown //= require turbolinks [...]
Devise 의 특정 flash message를 스타일링하기 위해 Sass @extend 메소드를 사용하자(이 메소드를 남용하지 말자. 이 지시자는 몇몇 결점이 있다):
application.scss
[...] .alert-alert { @extend .alert-warning; } .alert-notice { @extend .alert-info; } [...]
서버를 다시 읽어들이고 drop down 메뉴를 살펴보자. 왜 사용자이름이 표시되지 않는가?
String_params and Edit Profile Page
만약 string_params를 가진 Rails를 사용하고 있다면(기본으로 Rails 4에서는 enable되어 있다), 하나의 스텝이 더 필요하다::name 속성이 화이트 리스팅 되어야한다.
application_controller.rb
[...] before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_up) << :name devise_parameter_sanitizer.for(:account_update) << :name end [...]
우리는 모든 Devise controllers에 sign up과 account update 액션을 위한 :name 속성을 화이트리스팅하는 before_action을 추가하였다. 만약 중첩된 해쉬 또는 배열을 가진 약간 더 복잡한 시나리오를 가지고 있다면 문서의 이 부분을 참고하여라.
당신의 이름을 변경하기 위한 프로파일 페이지를 이끌어가기 전에 대응되는 뷰가 업데이트 되어야 한다:
views/devise/registrations/edit.html.erb
<% header "Edit #{resource_name.to_s.humanize}" %> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <div class="form-group"> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <span class="label label-info">Currently waiting confirmation for: <%= resource.unconfirmed_email %></span> <% end %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, autocomplete: "off", class: 'form-control' %> <span class="help-block">leave blank if you don't want to change it</span> </div> <div class="form-group"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %> </div> <div class="form-group"> <%= f.label :current_password %> <%= f.password_field :current_password, autocomplete: "off", class: 'form-control' %> <span class="help-block">we need your current password to confirm your changes</span> </div> <%= f.submit "Update", class: 'btn btn-primary' %> <% end %> <h3>Cancel my account</h3> <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), class: 'btn btn-danger', data: { confirm: "Are you sure?" }, method: :delete %> </p> <%= link_to "Back", :back, class: 'btn btn-default btn-sm' %>
사용자는 또한 그들의 이메일 주소, 비밀번호 또한 변경하길 원할 것이다(우리가 이전에 논의했듯이 확인이 필요하다) 그리고 심지어 프로파일을 완전히 삭제하길 원할 것이다.
다른 Devise의 뷰들은 어떻게 스타일링하는지는 보여주지 않을 거서이다. 관심이 있다면 GitHub repo 를 확인하라.
Sending E-mail and DelayedJob
이제 간단하게 이메일을 보내는 절차에 대해 논의해보자. 먼저 개발 환경에서 이메일은 기본으로 보내지지 않는다는 것을 기억해야 한다. 여전히 콘솔을 통해 코멘트처리 되어 있는 것을 볼 수 있을 것이다. enable 시켜보자.
config/environments/development.rb
[...] config.action_mailer.perform_deliveries = true [...]
그리고 ActionMailer를 설정하자(여기 예제가 있다)
다음으로 production을 위해 유사한 설정을 추가해야할 것이다. 여기 데모 앱을 위한 설정이 있다.
config/environments/production.rb
[...] config.action_mailer.delivery_method = :smtp config.action_mailer.default_url_options = { host: 'sitepoint-devise.herokuapp.com' } ActionMailer::Base.smtp_settings = { :address => 'smtp.sendgrid.net', :port => '587', :authentication => :plain, :user_name => ENV['SENDGRID_USERNAME'], :password => ENV['SENDGRID_PASSWORD'], :domain => 'heroku.com', :enable_starttls_auto => true } [...]
이 앱은 Heroku에서 실행되기 때문에 나는 그것의 메일 전송 add-ons 중 하나를 사용해야 했다(당신은 이메일을 곧바로 보낼 수 없을 것이다). 나의 설정에서 Sendgrid 가 사용된다. 이것은 free plan 상에서 실행되기 때문에 메일이 전송되는데 시간이 걸릴 것이다.
어째든, 당신은 커스텀 메일을 사용하기 원한다면, devise.rb에 있는 config.mailer를 수정하여라.
그리고 마지막으로 이메일을 보내기를 백그라운드로 실행하는 것이 좋은 아이디어이다. 반면에 사용자들은 새로운 페이지로 리다이렉트 되기 전에 메일이 보내질때까지 기다려야 할 것이다. Devise에 Delayedjob을 예제와 같이 통합해 보자.
새로운 gem을 추가하자:
Gemfile
[...] gem 'delayed_job_active_record' [...]
그리고 실행하자.
$ bundle install
$ rails generate delayed_job:active_record
$ rake db:migrate
gem 을 설치하고 필요한 마이그레이션을 생성하고 적용하기(생성될 scheduled task들을 저장할 새로운 테이블)
Rails4.2의 경우 queueing backend를 셋팅하기 위해 application.rb 파일을 변경해라
config/application.rb
[...] config.active_job.queue_adapter = :delayed_job [...]
이제, 당신 모델의 Devise의 메소드를 오버라이딩하자.
models/user.rb
[...] def send_devise_notification(notification, *args) devise_mailer.send(notification, self, *args).deliver_later end [...]
deliver_later 는 전송이 큐에 담길 것을 의미한다.
만약 앱을 Heroku에 배포하고자한다면, 당신 프로젝트 루트에 Profile을 생성하고 다음 라인을 추가하라.
Profile
worker: rake jobs:work
job을 처리하기 위해 적어도 하나의 worker 프로세스가 job을 처리하도록 해야할 것이고 그것은 한달에 $35의 경비가 소요될 것이다.
$ heroku ps:scale worker=1
Heroku에서 Delayedjob을 사용하는 것에 대한 매우 이해하기 쉬운 가이드가 있다. 더 자세한 사항은 그것을 참고하라.
데모 앱에서는 background sending을 enable 하지 않을 것이다. 그러나 관련 코드는 GitHub repo에 있다.
Resticting Access
여기까지 잘 따라왔다. 특정 페이지에 대해 접근을 제한하는 것은 어떻게 할까? 어떻게 구현할 수 있을까?Devise는 그것 또한 잘 다루는 것으로 보인다: 당신은 대응되는 메소드를 before_action 처럼사용하기만 하면 된다. 먼저 우리는 제한될 특별한 페이지가 필요하다. 그것을 간단히 "Secret" 이라고 부르자.
config/routes.rb
[...] get '/secret', to: 'pages#secret', as: :secret [...]
의미없는 간단한 뷰는 :
views/pages/secret.html.erb
<% header "Secret!" %>
당신은 심지어 secret 메소드를 PagesController에 추가할 필요조차 없다 - Rails는 그것을
암암리에 정의할 것이다.
메뉴를 약간 수정해보자:
views/layouts/application.html.erb
[...] <ul class="nav navbar-nav"> <li><%= link_to 'Home', root_path %></li> <% if user_signed_in? %> <li><%= link_to 'Secret', secret_path %></li> <% end %> </ul> [...]
그리고 controller를 수정해보자.
[...] before_action :authenticate_user!, only: [:secret] [...]
before_action은 secret 메소드를 호출하기 전에 사용자가 인증된 사용자인지 체크할 것이다. 미인증 사용자는 "Please authenticate" flash 메시지를 가진 페이지로 리다이렉트될 것이다. 이름이 다른 모델의 경우 이 메소드는 다른 이름을 가질 것이라는 것을 다시 한번 상기하자(Admin 모델의 경우 authenticate_admin!이다).
Using Devise Extensions
큰 Devise 커뮤니티는 더 많은 기능들을 추가하기 위해 많은 좋은 extensions 를 개발했다. Bit Zesty가 개발한 Devise Zxcvbn 을 시험삼아 통합해보자.이 extension은 페스워드 길이를 측정하고 약한 패스워드는 거부한다. 그것은 zxcvbn-ruby에 의존하는데 Dropbox에 의해 만들어진 zxcvbn.js의 루비 포팅버전이다. 어떻게 이 솔루션이 동작하는지 알고 싶으면 이 공식 블로그 포스트를 읽어라.
새 gem을 추가하자:
Gemfile
[...] gem 'devise_zxcvbn' [...]
그리고 다음을 실행한다.
$ bundle install
그리고 당신의 모델을 위한 새로운 모듈을 등록하자.
models/user.rb
[...] devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable, :zxcvbnable [...]
이제 User는 zxcvbnable 하다(그냥 발음해보아라). 이 extension을 위한 하나의 셋팅만 존재한다.
config/initializers/devise.rb
[...] config.min_password_score = 0 [...]
이는 기본적으로 패스워드가 얼마나 강력해야하는가를 의미한다(높은 수가 더 강력하다). Strength는 cracking time을 측정한것으로 이해하면 된다.
- 0 - estimated cracking time is less then 10**2 seconds.
- 1 - 10 ** 4 seconds.
- 2 - 10 ** 6 seconds.
- 3 - 10 ** 9 seconds.
- 4 - inifinity(글쎄, 사실은 이경우 몇 세기 정도로 생각할 수 있다)
사용자는 복잡한 패스워드를 생각할 필요가 없을 것이므로 여기서는 0으로 셋팅했다. 실제 앱에서는 그러한 약한 패스워드를 허락하는 것은 추천하지 않는다.
마지막으로 에러 메시지를 커스터마이징하자:
config/locales/devise.en.yml
[...] en: errors: messages: weak_password: "is not strong enough. Consider adding a number, symbols or more letters to make it stronger." [...]
다양한 패스워드를 시도해보고 얼마다 그들이 강력한지 체크해보자.
Measuring Passwords' Strength on Client-Side
마지막으로, zxcvbn을 클라이언트 사이드에 어떻게 적용하는지 보여주겠다. 이 파일을 다운로드해서 당신의 프로젝트에 연결해보자. 다음 컨텐츠를 가진 새로운 CoffeeScript 파일을 생성해보자.global.coffee
jQuery -> displayHint = (strength, crack_time) -> msg = 'Password is ' + strength + ' (time to break it: ' + crack_time + ')' estimate_message = this.next('.estimate-message') if estimate_message.length < 0 estimate_message.text msg else this.after '<span class="help-block estimate-message">' + msg + '</span>' $('form').on 'keyup', '.estimate-password', -> $this = $(this) estimation = zxcvbn($this.val()) crack_time = estimation.crack_time_display switch estimation.score when 0 then displayHint.call($this, "very weak", crack_time) when 1 then displayHint.call($this, "weak", crack_time) when 2 then displayHint.call($this, "okay", crack_time) when 3 then displayHint.call($this, "strong", crack_time) when 4 then displayHint.call($this, "very strong", crack_time) return
그리고 연결해보자.
application.js
[...] //= require global [...]
score 는 0에서 4까지의 수를 리턴 한다 - 우리는 조금 전에 그 값들에 대해 얘기했었다 - 그리고 그것은 strength를 측정하는 가장 편리한 방법으로 보인다. crack_time_display는 친근한 방식으로 패스워드를 크랙하는데 필요한 시간 측정값을 리턴한다("3days", "4days", "centuries" 등등).https://github.com/plataformatec/devise/wiki/How-Tos
이제 estimate-password 클래스를 원하는 아무 패스워드 필드에 할당해보자. 예를 들어
views/devise/registrations/new.html.erb
[...] <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, autocomplete: "off", class: 'form-control estimate-password' %> <% if @validatable %> <span class="help-block"<>%= @minimum_password_length %> characters minimum</span> <% end %> </div> <div class="form-group"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control estimate-password' %> </div> [...]
물론, 이것은 가장 간단한 솔루션이다. 그러므로 이것을 자유롭게 확장해라.
Conclusion
이제 이 문서의 끝에 도달했다. 우리는 Devise를 앱에 통합했다. 그것의 모듈과 셋팅을 살펴보았고 extension을 추가했다. 사용가능한 더 많은 특성과 extension 들이 있다. 그러므로 나는 official Wiki 를 살펴볼 것을 장려한다. 특별히 How-to's 유심히 보아라 - 거기는 90개 이상의 작지만 유용한 튜토리얼이 있다.난 Rails 시리즈와 함께한 Authentication 의 두번째 파트를 당신들이 즐겼기를 바란다. 다음 문서에서 우리는 Oauth2와 함께한 Authentication에 대해 논의할 것이다.
출처 http://www.sitepoint.com/devise-authentication-in-depth/
댓글 없음:
댓글 쓰기