BLOG main image
분류 전체보기 (228)
Rails (63)
Ruby (34)
이야기 (34)
스토리큐 (61)
그 밖에.. (27)
C# (6)
마사키군의 생각
ayukawa's me2DAY
작은아이의 생각
agiletalk's me2DAY
[Google App Engine] 나의 첫번..
머드초보의 블로그
ruby on rails에서 OpenID 사용..
Sinan's Present - Flex/AIR, Ja..
Rails로 Openid 로그인 기능 구..
Extremely Agile
39,480 Visitors up to today!
Today 6 hit, Yesterday 35 hit

 SUBSCRIBE

2008/04/06 19:43

하고자 하는 것들

restful authentication plugin과 open_id_authentication plugin을 이용해서 openid를 이용한 login과정을 구현한다.

restful authentication을 이용해서 회원 가입 과정은 구현하지 않을 것이다. 

첫번째로 openid를 이용해서 로그인하는 가입자는 user table에 저장한다. 이 때 name과 email도 같이 저장한다.

로그인 아이디 기억하기(자동 로그인) 기능을 구현한다.

로그인 입력 창에 openid 로고를 넣는다.

 

gem과 plugin의 설치

ruby-openid gem  설치한다.

  1. gem install ruby-openid

 

open id authentication plugin 설치힌다.

  1. ruby script\plugin install open_id_authentication

restful authentication plugin 을 설치한다.

  1. ruby script\plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/

 

migration 을 이용한 db table 생성

restful authentication의 generate를 이용해서 user와 sessions를 생성한다.

  1. ruby script/generate authenticated user sessions

위 명령은 user model 과 sessions controller, users controller 를 생성한다. users controller가 필요없다면 삭제해도 좋다.

 

user table에 identity_url을 추가한다.

  1. # db/migrate/001_create_users.rb
  2. t.column :identity_url, :string
  3. t.column :name, :string

 

rake를 이용해서 open_id_authentication migration을 추가한다.

  1. rake open_id_authentication:db:create

db/migrate/002_add_open_id_authentication_tables.rb  파일이 생성된다.

 

migrate를 수행해 db table을 생성한다.

  1. rake db:migrate

 

기타 설정

application controller에 다음을 추가한다. 기본적으로 모든 controller들의 요청이 login_required에 의해 login을 요구하도록 하고 있다.

  1. include AuthenticatedSystem 
    before_filter :login_required

 

login이 필요없는 controller 혹은 action에 대해서는 skip_before_filter를 이용할 수 있다.

sessions controller는 login_required가 필요없으니 다음을 추가한다. 

  1. skip_before_filter :login_required

 

routes.rb를 다음과 같이 한다.

  1. map.resources :users
    map.open_id_complete 'session', :controller => "sessions", :action => "create", :conditions => { :method => :get }
    map.resource :session

map.open_id_complete는 openid provider로 부터 인증이 완료되었음을 알리는 request를 sessions controller의 create action으로 보내기 위함이다. 이 요청은 open_id_authentication plugin에서는 GET "/session?각종_파라미터들" 이다. 그래서 /session에 대한 get 요청을 create action으로 보낸다. 이것이 마음에 들지 않는다면 return_to option을 조절해서 원하는 값으로 바꿀 수 있다. return_to option에 대해서는 밑에 다시 설명한다.

 

view의 구현

views/sessions/new.html.erb 를 다음과 같이 한다.

  1. <%= stylesheet_link_tag "login"  %>

    <div class="message">
      <%= logged_in? ? "로그인되었습니다." : "로그아웃 상태입니다." %>
      <%= link_to "로그아웃", session_url, :method=>:delete if logged_in?  %>
    </div>

    <% form_tag(session_url) do %>
      <label for="openid_url">OpenID:</label>
      <%= text_field_tag "openid_identifier", "", :class=>"openid" %>
      <br/>
      <%= check_box_tag 'remember_me' %>
       <label for="remember_me" class="rememberMe">아이디 기억하기</label>
    <% end %>
  • login.css를 포함하고,
  • 로그인 여부를 보여주고,
  • openid를 입력할 수 있는 form을 보여주고, openid 권장 표준 스타일 가이드에 따르면 openid input의 이름을 openid_indentifier로 하라고 한다.
  • 아이디 기억하기 checkbox를 보여준다.

 

login.css를 다음과 같이 작성해서 style을 준다.

  1. body {
        margin: 50px 0px 0px 0px;
        text-align: center;  
        font-size: 14px;
    }
    div.message {
        margin: 10px 0px;
    }
    input.openid {
        background:transparent url(/images/openid_bg.gif) no-repeat left center;
        padding-left: 18px;
        border: 1px solid;
    }

openid logo(openid_bg.gif)는 http://openid.net/logos/ 에서 적당한 것으로 다운 받자.

 

model의 구현

user model은 restful authentication에 의해 이미 생성되어 있다. openid 인증을 적용하기 위해서는 약간의 수정이 필요한데, 그 내용은 다음과 같다.( 변경된 내용만 적는다. )

  1.   validates_presence_of     :email
      validates_presence_of     :login, :if => :not_openid?
  2.   validates_length_of       :login,    :within => 3..40, :if => :not_openid?
  3.   validates_uniqueness_of   :login, :email, :case_sensitive => false, :allow_nil => true
  4.  
  5.   attr_accessible :login, :email, :password, :password_confirmation, :identity_url  
  6.  
  7.     def password_required?
          not_openid? && (crypted_password.blank? or not password.blank?)
        end   
        def not_openid?
          identity_url.blank?
        end

주된 내용은 "openid를 이용하는 사용자에게는 login, password같이 필요없는 것들에 대한 validation check을 하는 않는다." 이다.

 

Controller 의 구현

sessions controller의 내용은 꽤 길다.

  1. class SessionsController < ApplicationController
      skip_before_filter :login_required
     
      def create
        if using_open_id? params[:openid_identifier]
          open_id_authentication params[:openid_identifier]
        else
          password_authentication(params[:login], params[:password])
        end
      end
     
      def destroy
        self.current_user.forget_me if logged_in?
        cookies.delete :auth_token
        reset_session
        flash[:notice] = "로그아웃 되었습니다."
        redirect_back_or_default('/')
      end

      protected
      def password_authentication(login, password)
        self.current_user = User.authenticate(login, password)
        if logged_in?
          successful_login
        else
          failed_login "Sorry, that username/password doesn't work"
        end
      end

      def open_id_authentication(openid_url)   
        authenticate_with_open_id(openid_url, :required=>[:nickname, :email],
            :return_to=>openid_return_url) do |result, identity_url, registration|
          if result.successful?
            @user = User.find_or_initialize_by_identity_url(identity_url)       
            if @user.new_record?         
              @user.name = registration['nickname']
              @user.email = registration['email']
              @user.save #TODO : need to check validation
            end       
            self.current_user = @user
            successful_login      
          else
            failed_login result.message
          end
        end
      end

      private 
      def openid_return_url
        "#{request.protocol + request.host_with_port + request.relative_url_root + request.path}" +
          "?remember_me=#{params[:remember_me]}"
      end
     
      def successful_login   
        if params[:remember_me] == "1"
          self.current_user.remember_me     
          cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
        end
        redirect_to(root_url)
      end

      def failed_login(message)
        flash[:error] = message
        redirect_to(new_session_url)
      end
    end

openid_identifier가 있다면 이 사용자는 openid를 이용해서 로그인을 하는 것이다. 만약 그렇지 않다면 일반적인 login/password로 로그인을 하려는 것이다. login/password 인증이 필요없다면 password_authentication 를 빼도 된다.

로그아웃 과정은 평범하다.

open_id_authentication 를 살펴보면,

인증을 하면서 nickname과 email을 요청한다.

  1.     authenticate_with_open_id(openid_url, :required=>[:nickname, :email],
            :return_to=>openid_return_url) do |result, identity_url, registration|

만약 처음으로 로그인하는 사용자라면 user 를 하나 생성하고 name과 email을 저장한다.

  1.         @user = User.find_or_initialize_by_identity_url(identity_url)       
            if @user.new_record?         
              @user.name = registration['nickname']
              @user.email = registration['email']
              @user.save #TODO : need to check validation
            end  

인증이 잘 되었다면 current_user를 user로 해 주자.

 

로그인 기억하기 구현

openid provider가 인증을 마치고 응답을 줄때는 :return_to 의 값으로 응답한다.(꼭 :return_to의 값은 아니다. 여기에 다른 파라미터들도 여러개 붙는다.) (진이가 오늘 이쁜짓을 많이하네.. 이제 정말 고양이 스럽다. 난 그 깊은 속을 알 길이 없구나.) 여기서 return_to의 값으로 정의한 openid_return_url은 remember_me 가 parameter로 포함되어 있다. 그래서 "http://site.co.kr/sessions?remember_me=1"  같은 모양이다. 만약 응답 url이 "/sessions 인 것이 마음에 들지 않는다면 원하는 값으로 수정할 수도 있다. 수정 후에는 routes.rb에 그 내용을 써주는 것을 잊지 말자.

  1.     authenticate_with_open_id(openid_url, :required=>[:nickname, :email],
            :return_to=>openid_return_url) do |result, identity_url, registration|
  2. ...
  3.   def openid_return_url
        "#{request.protocol + request.host_with_port + request.relative_url_root + request.path}" +
          "?remember_me=#{params[:remember_me]}"
      end

:return_to options을 굳이 적어주는 이유는 remember_me 정보를 보전하기 위함이다. 이 정보는 인증이 잘 이루어진 후 로그인 정보를 기억할지를 결정하는 데 사용된다.

  1.   def successful_login   
        if params[:remember_me] == "1"
          self.current_user.remember_me     
          cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
        end
        redirect_to(root_url)
      end

 

써보니

와.. 생각보다 훨씬 간단하다.

난 아직 openid 스펙도 잘 모르는데, 이 기능을 구현할 수 있다는 사실이 놀랍고, 두렵다. 

(진이는 커가면서 어리광이 느는 것 같다.)

 

참조

acts as authentication plugin과 openid 를 같이 사용하기

openid 권장표준 스타일

openid 2.0 스펙

railscast

http://shinji.springnote.com/pages/475676

이 글은 스프링노트에서 작성되었습니다.