Rails에 대한 이메일 검증의 최첨단은 무엇입니까?


95

사용자의 이메일 주소를 확인하기 위해 무엇을 사용하고 있으며 그 이유는 무엇입니까?

validates_email_veracity_of실제로 MX 서버를 쿼리하는 것을 사용했습니다 . 그러나 이는 대부분 네트워크 트래픽 및 안정성과 관련된 다양한 이유로 실패로 가득 차 있습니다.

주위를 둘러 보니 많은 사람들이 이메일 주소에 대한 온 전성 검사를 수행하는 데 사용하고 있다는 분명한 사실을 찾을 수 없었습니다. 이를 위해 유지되고 합리적으로 정확한 플러그인이나 gem이 있습니까?

추신 : 이메일이 작동하는지 확인하기 위해 링크가있는 이메일을 보내라고 말하지 마십시오. "친구에게 보내기"기능을 개발 중이므로 실용적이지 않습니다.


다음은 정규식을 다루지 않고 매우 쉬운 방법입니다.
detection

MX 서버 쿼리가 실패한 이유를 더 자세히 설명해 주시겠습니까? 이것들이 고칠 수 있는지 알 수 있도록 알고 싶습니다.
lulalala

답변:


67

Rails 3.0에서는 Mail gem을 사용하여 regexp없이 이메일 유효성 검사를 사용할 수 있습니다 .

여기에 내 구현이 있습니다 ( gem 패키지 ).


좋아, 나는 당신의 보석을 사용하고 있습니다. 감사.
jasoncrawford

처럼 보인다 ###@domain.com유효 할 습니까?
cwd

1
이 보석을 되 살리고 싶은 여러분, 그것을 유지할 시간이 없었습니다. 그러나 사람들은 여전히 ​​그것을 사용하고 개선을 찾는 것 같습니다. 관심이 있으시면 github 프로젝트에 글을 남겨주세요 : hallelujah / valid_email
Hallelujah

106

필요 이상으로 어렵게 만들지 마십시오. 귀하의 기능은 중요하지 않습니다. 유효성 검사는 오타를 잡기위한 기본적인 온전한 단계 일뿐입니다. 나는 간단한 정규식으로 그것을 할 것이고 너무 복잡한 것에 CPU 사이클을 낭비하지 않을 것입니다.

/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/

이것은 http://www.regular-expressions.info/email.html 에서 수정되었습니다 -모든 장단점을 정말로 알고 싶다면 읽어야합니다. 더 정확하고 훨씬 더 복잡한 RFC822 호환 정규식을 원한다면 그 페이지에도 있습니다. 그러나 문제는 이것이다 : 당신이 그것을 완전히 옳게 할 필요는 없습니다.

주소가 유효성 검사를 통과하면 이메일을 보내 게됩니다. 이메일이 실패하면 오류 메시지가 표시됩니다. 어느 시점에서 사용자에게 "죄송합니다. 친구가받지 못했습니다. 다시 시도 하시겠습니까?" 라고 말할 수 있습니다.직접 검토를 위해 플래그를 지정하거나 무시할 수 있습니다.

이것은 주소 가 그랬 다면 다루어야 할 것과 동일한 옵션입니다. 유효성 검사를 통과 입니다. 유효성 검사가 완벽하고 주소가 존재한다는 절대적인 증거를 획득하더라도 전송이 여전히 실패 할 수 있기 때문입니다.

검증시 오 탐지 비용은 낮습니다. 더 나은 유효성 검사의 이점도 낮습니다. 관대하게 검증하고 오류가 발생하면 걱정하십시오.


36
.museum과 새로운 국제 TLD에 대한 이야기가 아닐까요? 이 정규식은 많은 유효한 이메일 주소를 차단 합니다.
Elijah

3
엘리야의 말에 동의하는 것은 나쁜 권고입니다. 또한 이메일이 즉시 성공했는지 알 수있는 방법이 없기 때문에 친구가 이메일을받지 못했다고 사용자에게 어떻게 말할 수 있을지 모르겠습니다.
Jaryl

8
.museum 등에 대한 좋은 점 은 2009 년에 처음 답변을 올렸을 때 문제가되지 않았습니다. 정규식을 변경했습니다. 추가 개선 사항이있는 경우 편집하거나 커뮤니티 위키 게시물로 만들 수 있습니다.
SFEley

5
참고로, 여전히 유효한 이메일 주소가 누락됩니다. 많지는 않지만 소수입니다. 예를 들어 기술적으로 #|@foo.com은 유효한 이메일 주소이며 "따옴표로 묶으면 공백을 사용할 수 있습니다."@ foo.com입니다. @ 앞의 모든 것을 무시하고 도메인 부분 만 확인하는 것이 가장 쉽습니다.
Nerdmaster

6
잘못된 주소를 허용하는 것에 대해 걱정할 필요가 없다는 동기에 동의합니다. 안타깝게도이 정규식은 일부 올바른 주소를 허용하지 않습니다. 아마도 이와 같은 것이 더 좋을까요? /.+@.+\..+/
ZoFreX

12

Rails 3에서 이메일 유효성 검사를위한 gem을 만들었습니다. Rails가 기본적으로 이와 같은 것을 포함하지 않는다는 사실에 약간 놀랐습니다.

http://github.com/balexand/email_validator


8
이것은 본질적으로 정규식을 둘러싼 래퍼입니다.
Rob Dawson

if또는 unless문과 함께 이것을 사용하는 방법에 대한 예를 제공 할 수 있습니까 ? 문서가 드물게 보입니다.
cwd

@cwd 문서가 완전하다고 생각합니다. Rails 3 이상 검증에 익숙하지 않다면이 Railscast ( railscasts.com/episodes/211-validations-in-rails-3 ) 또는 guides.rubyonrails.org/active_record_validations.html
balexand


7

가에서 4 문서를 레일 :

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end

class Person < ActiveRecord::Base
  validates :email, presence: true, email: true
end

5

Rails 4에서는 모델에 간단히 추가 validates :email, email:true(필드가라고 가정 email) 한 다음 단순 (또는 복합 †)을 작성합니다.EmailValidator 필요에 맞게 을 작성하십시오.

예 :-모델 :

class TestUser
  include Mongoid::Document
  field :email,     type: String
  validates :email, email: true
end

귀하의 검증 인 (으로 이동 app/validators/email_validator.rb)

class EmailValidator < ActiveModel::EachValidator
  EMAIL_ADDRESS_QTEXT           = Regexp.new '[^\\x0d\\x22\\x5c\\x80-\\xff]', nil, 'n'
  EMAIL_ADDRESS_DTEXT           = Regexp.new '[^\\x0d\\x5b-\\x5d\\x80-\\xff]', nil, 'n'
  EMAIL_ADDRESS_ATOM            = Regexp.new '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+', nil, 'n'
  EMAIL_ADDRESS_QUOTED_PAIR     = Regexp.new '\\x5c[\\x00-\\x7f]', nil, 'n'
  EMAIL_ADDRESS_DOMAIN_LITERAL  = Regexp.new "\\x5b(?:#{EMAIL_ADDRESS_DTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x5d", nil, 'n'
  EMAIL_ADDRESS_QUOTED_STRING   = Regexp.new "\\x22(?:#{EMAIL_ADDRESS_QTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x22", nil, 'n'
  EMAIL_ADDRESS_DOMAIN_REF      = EMAIL_ADDRESS_ATOM
  EMAIL_ADDRESS_SUB_DOMAIN      = "(?:#{EMAIL_ADDRESS_DOMAIN_REF}|#{EMAIL_ADDRESS_DOMAIN_LITERAL})"
  EMAIL_ADDRESS_WORD            = "(?:#{EMAIL_ADDRESS_ATOM}|#{EMAIL_ADDRESS_QUOTED_STRING})"
  EMAIL_ADDRESS_DOMAIN          = "#{EMAIL_ADDRESS_SUB_DOMAIN}(?:\\x2e#{EMAIL_ADDRESS_SUB_DOMAIN})*"
  EMAIL_ADDRESS_LOCAL_PART      = "#{EMAIL_ADDRESS_WORD}(?:\\x2e#{EMAIL_ADDRESS_WORD})*"
  EMAIL_ADDRESS_SPEC            = "#{EMAIL_ADDRESS_LOCAL_PART}\\x40#{EMAIL_ADDRESS_DOMAIN}"
  EMAIL_ADDRESS_PATTERN         = Regexp.new "#{EMAIL_ADDRESS_SPEC}", nil, 'n'
  EMAIL_ADDRESS_EXACT_PATTERN   = Regexp.new "\\A#{EMAIL_ADDRESS_SPEC}\\z", nil, 'n'

  def validate_each(record, attribute, value)
    unless value =~ EMAIL_ADDRESS_EXACT_PATTERN
      record.errors[attribute] << (options[:message] || 'is not a valid email')
    end
  end
end

이렇게하면 태그를 포함한 모든 종류의 유효한 이메일이 허용됩니다. "test+no_really@test.tes"등과 같은 이메일을 됩니다.

이것을 테스트하려면 rspecspec/validators/email_validator_spec.rb

require 'spec_helper'

describe "EmailValidator" do
  let(:validator) { EmailValidator.new({attributes: [:email]}) }
  let(:model) { double('model') }

  before :each do
    model.stub("errors").and_return([])
    model.errors.stub('[]').and_return({})  
    model.errors[].stub('<<')
  end

  context "given an invalid email address" do
    let(:invalid_email) { 'test test tes' }
    it "is rejected as invalid" do
      model.errors[].should_receive('<<')
      validator.validate_each(model, "email", invalid_email)
    end  
  end

  context "given a simple valid address" do
    let(:valid_simple_email) { 'test@test.tes' }
    it "is accepted as valid" do
      model.errors[].should_not_receive('<<')    
      validator.validate_each(model, "email", valid_simple_email)
    end
  end

  context "given a valid tagged address" do
    let(:valid_tagged_email) { 'test+thingo@test.tes' }
    it "is accepted as valid" do
      model.errors[].should_not_receive('<<')    
      validator.validate_each(model, "email", valid_tagged_email)
    end
  end
end

어쨌든 이것이 내가 한 방법입니다. YMMV

† 정규 표현은 폭력과 같습니다. 작동하지 않으면 충분히 사용하지 않는 것입니다.


1
나는 당신의 검증을 사용하고 싶지만 당신이 그것을 어디서 얻었는지, 어떻게 만들 었는지 전혀 모릅니다. 말씀해 주시겠습니까?
Mauricio Moraes 2014 년

Google 검색에서 정규식을 가져와 직접 래퍼 코드와 사양 테스트를 작성했습니다.
Dave Sag 2014 년

1
테스트도 게시 한 것이 좋습니다! 하지만 정말로 나를 얻은 것은 저기 파워 따옴표였습니다! :)
Mauricio Moraes 2014 년

4

Hallelujah가 제안 했듯이 Mail gem을 사용하는 것 같습니다. 을 사용하는 것이 좋은 접근 방식 . 그러나 나는 거기에 몇 가지 농구를 싫어합니다.

나는 사용한다:

def self.is_valid?(email) 

  parser = Mail::RFC2822Parser.new
  parser.root = :addr_spec
  result = parser.parse(email)

  # Don't allow for a TLD by itself list (sam@localhost)
  # The Grammar is: (local_part "@" domain) / local_part ... discard latter
  result && 
     result.respond_to?(:domain) && 
     result.domain.dot_atom_text.elements.size > 1
end

TLD (최상위 도메인)를 이 목록 에 포함하도록 요구하면 더 엄격해질 수 있지만 2012 년 추가 .mobi.tel ).

파서를 직접 연결하는 장점은 Mail 문법규칙 이 Mail gem이 사용하는 부분에 대해 상당히 넓다 user<user@example.com>는 것입니다. SMTP에 일반적으로 사용 되는 주소를 구문 분석 할 수 있도록 설계되었습니다 . 그것을 소비함으로써Mail::Address 당신은 많은 추가 검사를해야합니다.

Mail gem에 대한 또 다른 참고 사항은 클래스가 RFC2822이지만 문법에는 RFC5322의 일부 요소가 있습니다 ( 예 : this test) .


1
이 스 니펫에 감사드립니다, 샘. Mail gem이 제공하는 일반적인 "대부분의 경우 충분히 좋은"유효성 검사가 없다는 것에 조금 놀랐습니다.
JD.

4

Rails 3에서는 재사용 가능한 문서를 작성할 수 있습니다. 유효성 검사기 .

http://archives.ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators

class EmailValidator < ActiveRecord::Validator   
  def validate()
    record.errors[:email] << "is not valid" unless
    record.email =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i   
  end
end

다음과 함께 사용하십시오 validates_with.

class User < ActiveRecord::Base   
  validates_with EmailValidator
end

3

다른 답변에 주목하면서 질문은 여전히 ​​남아 있습니다. 왜 그것에 대해 영리하게 생각 하는가?

많은 정규식이 거부하거나 놓칠 수있는 실제 사례의 양은 문제가있는 것처럼 보입니다.

이메일 주소를 '검증'하더라도 실제로 작동하는 이메일 주소인지 확인하는 것은 아닙니다.

regexp를 사용하는 경우 클라이언트 측에 @이 있는지 확인하십시오.

잘못된 이메일 시나리오의 경우 '메시지를 보내지 못했습니다'분기를 코드로 보내십시오.


1

기본적으로 가장 일반적인 3 가지 옵션이 있습니다.

  1. 정규 표현식 (일괄적인 전자 메일 주소 정규 표현식이 없으므로 직접 굴림)
  2. MX 쿼리 (사용하는 것)
  3. 활성화 토큰 생성 및 우편 발송 (restful_authentication 방식)

validates_email_veracity_of와 토큰 생성을 모두 사용하지 않으려면 구식 정규식 검사를 사용합니다.


1

Mail gem에는 주소 파서가 내장되어 있습니다.

begin
  Mail::Address.new(email)
  #valid
rescue Mail::Field::ParseError => e
  #invalid
end

Rails 3.1에서 나를 위해 작동하지 않는 것 같습니다. Mail :: Address.new ( "john")은 예외없이 새 Mail :: Address 객체를 기쁘게 반환합니다.
jasoncrawford

좋아, 일부 경우 예외가 발생하지만 전부는 아닙니다. @Hallelujah의 링크는 여기에 좋은 접근 방식을 가지고있는 것 같습니다.
jasoncrawford

1

이 솔루션은 @SFEley 및 @Alessandro DS의 답변, 리팩터링 및 사용 설명을 기반으로합니다.

다음과 같이 모델에서이 유효성 검사기 클래스를 사용할 수 있습니다.

class MyModel < ActiveRecord::Base
  # ...
  validates :colum, :email => { :allow_nil => true, :message => 'O hai Mark!' }
  # ...
end

app/validators폴더에 다음이있는 경우 (레일 3) :

class EmailValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    return options[:allow_nil] == true if value.nil?

    unless matches?(value)
      record.errors[attribute] << (options[:message] || 'must be a valid email address')
    end
  end

  def matches?(value)
    return false unless value

    if /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value).nil?
      false
    else
      true
    end

  end
end

1

대한 메일 링리스트의 검증 . (저는 Rails 4.1.6을 사용합니다)

여기 에서 내 정규식을 얻었 습니다. . 매우 완벽한 것으로 보이며 수많은 조합에 대해 테스트되었습니다. 해당 페이지에서 결과를 볼 수 있습니다.

Ruby 정규 표현식으로 약간 변경하고 내 lib/validators/email_list_validator.rb

코드는 다음과 같습니다.

require 'mail'

class EmailListValidator < ActiveModel::EachValidator

  # Regexp source: https://fightingforalostcause.net/content/misc/2006/compare-email-regex.php
  EMAIL_VALIDATION_REGEXP   = Regexp.new('\A(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))\z', true)

  def validate_each(record, attribute, value)
    begin
      invalid_emails = Mail::AddressList.new(value).addresses.map do |mail_address|
        # check if domain is present and if it passes validation through the regex
        (mail_address.domain.present? && mail_address.address =~ EMAIL_VALIDATION_REGEXP) ? nil : mail_address.address
      end

      invalid_emails.uniq!
      invalid_emails.compact!
      record.errors.add(attribute, :invalid_emails, :emails => invalid_emails.to_sentence) if invalid_emails.present?
    rescue Mail::Field::ParseError => e

      # Parse error on email field.
      # exception attributes are:
      #   e.element : Kind of element that was wrong (in case of invalid addres it is Mail::AddressListParser)
      #   e.value: mail adresses passed to parser (string)
      #   e.reason: Description of the problem. A message that is not very user friendly
      if e.reason.include?('Expected one of')
        record.errors.add(attribute, :invalid_email_list_characters)
      else
        record.errors.add(attribute, :invalid_emails_generic)
      end
    end
  end

end

그리고 모델에서 다음과 같이 사용합니다.

validates :emails, :presence => true, :email_list => true

다른 구분 기호와 synthax를 사용하여 다음과 같은 메일 링리스트의 유효성을 검사합니다.

mail_list = 'John Doe <john@doe.com>, chuck@schuld.dea.th; David G. <david@pink.floyd.division.bell>'

이 정규식을 사용하기 전에을 사용 Devise.email_regexp했지만 매우 간단한 정규식이며 필요한 모든 경우를 얻지 못했습니다. 일부 이메일이 충돌했습니다.

웹에서 다른 정규식을 시도했지만 지금까지 가장 좋은 결과를 얻었습니다. 귀하의 경우에 도움이되기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.