Cách xác thực một địa chỉ email trong PHP


218

Tôi có chức năng này để xác thực một địa chỉ email:

function validateEMAIL($EMAIL) {
    $v = "/[a-zA-Z0-9_-.+]+@[a-zA-Z0-9-]+.[a-zA-Z]+/";

    return (bool)preg_match($v, $EMAIL);
}

Điều này có ổn để kiểm tra xem địa chỉ email có hợp lệ hay không?


1
Nếu nó hoạt động nó hoạt động. Bạn thực sự không thể làm cho nó tốt hơn, nó quá nhỏ. Chỉ có điều không tốt là phong cách. validateEmailsẽ được sửa chữa, cũng như vượt qua $email, không $EMAIL.
Stan

Chỉ muốn chắc chắn rằng tôi không có bất kỳ vấn đề lớn nào trong mã đó :)
Cameron

Xem thêm stackoverflow.com/questions/201323/ trên để biết thêm về cách và cách không sử dụng biểu thức thông thường để xác thực địa chỉ email.
Legoscia

5
Điều đó sẽ không xác nhận nhiều địa chỉ email hợp lệ. Ví dụ: *@example.com hoặc'@example.com hoặc tôi @ [127.0.0.1] hoặc bạn @ [ipv6: 08B0: 1123: AAAA :: 1234]
jcoder

7
@jcoder, không phải tôi khuyên bạn nên dùng regex đó, nhưng ít nhất chúng ta có thể hy vọng bất cứ ai sử dụng địa chỉ đó để hát, v.v. sẽ không phàn nàn khi thất bại :)
Halil zgür

Câu trả lời:


568

Cách dễ nhất và an toàn nhất để kiểm tra xem địa chỉ email có được định dạng tốt hay không là sử dụng filter_var()chức năng:

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    // invalid emailaddress
}

Ngoài ra, bạn có thể kiểm tra xem tên miền có xác định MXbản ghi hay không:

if (!checkdnsrr($domain, 'MX')) {
    // domain is not valid
}

Nhưng điều này vẫn không đảm bảo rằng thư tồn tại. Cách duy nhất để tìm ra điều đó là bằng cách gửi thư xác nhận.


Bây giờ bạn đã có câu trả lời dễ dàng, hãy đọc về xác thực địa chỉ email nếu bạn muốn tìm hiểu hoặc chỉ cần sử dụng câu trả lời nhanh và tiếp tục. Không cảm thấy khó khăn.

Cố gắng xác thực địa chỉ email bằng regex là một nhiệm vụ "không thể". Tôi sẽ đi xa hơn để nói rằng regex bạn đã thực hiện là vô dụng. Có ba rfc liên quan đến các địa chỉ email và viết một biểu thức chính quy để bắt các địa chỉ email sai và đồng thời không có kết quả sai là điều không thể làm được. Kiểm tra danh sách này để kiểm tra (cả thất bại và thành công) của biểu thức chính được sử dụng bởi filter_var()chức năng của PHP .

Ngay cả các chức năng PHP tích hợp, ứng dụng email hoặc máy chủ cũng không được thực hiện đúng. Vẫn trong hầu hết các trường hợp filter_varlà lựa chọn tốt nhất.

Nếu bạn muốn biết mẫu biểu thức regex nào mà PHP (hiện tại) sử dụng để xác thực các địa chỉ email, hãy xem nguồn PHP .

Nếu bạn muốn tìm hiểu thêm về địa chỉ email, tôi khuyên bạn nên bắt đầu đọc thông số kỹ thuật, nhưng tôi phải cảnh báo bạn rằng đây không phải là một cách dễ dàng để đọc được:

Lưu ý rằng filter_var()như đã nêu chỉ có sẵn kể từ PHP 5.2. Trong trường hợp bạn muốn nó hoạt động với các phiên bản PHP trước đó, bạn có thể sử dụng biểu thức chính được sử dụng trong PHP:

<?php

$pattern = '/^(?!(?:(?:\\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}))\\]))$/iD';

$emailaddress = 'test@gmail.com';

if (preg_match($pattern, $emailaddress) === 1) {
    // emailaddress is valid
}

PS Một lưu ý về mẫu biểu thức chính được sử dụng ở trên (từ nguồn PHP). Có vẻ như có một số bản quyền về nó của Michael Rushton . Như đã nêu: "Hãy sử dụng và phân phối lại mã này. Nhưng vui lòng giữ thông báo bản quyền này."


Câu trả lời hay, nhưng theo liên kết này: haacked.com/archive 2007/08/21 / Tên người dùng o phần địa phương có thể được trích dẫn chuỗi, nhưng FILTER_VALIDATE_EMAIL không chấp nhận nó.
Daniel De León

3
Nó không hoạt động cho tất cả các địa chỉ email như đã nêu. Cũng xem danh sách các bài kiểm tra thất bại trong câu trả lời của tôi để thấy rằng một số chuỗi trích dẫn có hoạt động và một số khác thì không.
PeeHaa

4
Không, quá nhiều thử nghiệm thất bại trên mẫu đó emailtester.pieterhordijk.com/test-potype/MTAz :-)
PeeHaa

1
Mẫu này cực kỳ phức tạp trong trường hợp bạn cần sử dụng nó với chức năng như "preg_match_all" trên chuỗi văn bản lớn có email bên trong. Nếu bất kỳ bạn có đơn giản hơn xin vui lòng chia sẻ. Ý tôi là nếu bạn muốn: preg_match_all ($ mẫu, $ text_ chuỗi, $ khớp); sau đó mẫu phức tạp này sẽ làm quá tải máy chủ nếu bạn cần phân tích văn bản thực sự lớn.
Vlado

4
@PeeHaa: Postfix 3.0 hỗ trợ nó trong gần hai năm nay: postfix.org/SMTPUTF8_README.html , và nó được bao gồm trong Ubuntu 16.04 và sẽ được đưa vào bản phát hành Debian tiếp theo. Exim có hỗ trợ thử nghiệm. Các nhà cung cấp dịch vụ webmail như Gmail cũng đã thêm hỗ trợ để gửi / nhận các email như vậy, mặc dù bạn chưa thể tạo tài khoản unicode. Việc sử dụng và hỗ trợ rộng rãi là trong tầm tay và filter_varsẽ bị tụt lại khá lâu, ngay cả khi họ thay đổi ngay bây giờ (tôi đã đăng báo cáo lỗi).
Muỗi

43

Bạn có thể sử dụng bộ lọc_var cho việc này.

<?php
   function validateEmail($email) {
      return filter_var($email, FILTER_VALIDATE_EMAIL);
   }
?>

1
dừng thêm chức năng này vì điều này không xác nhận tên miền. nếu bạn đang thêm một số địa chỉ @ thì đây là hợp lệ. và không phải vậy!
Herr Nentu '

Điều gì với tất cả các hàm một dòng chứa các hàm một dòng? Tôi đang nhìn thấy chúng ở khắp mọi nơi. Khi nào thì điều này trở thành một "thứ"? (hùng biện). Điều này cần phải dừng lại.
Blue Water

15

Theo kinh nghiệm của tôi, regexcác giải pháp có quá nhiều dương tính giả và filter_var()các giải pháp có những tiêu cực sai (đặc biệt là với tất cả các TLD mới hơn ).

Thay vào đó, tốt hơn là đảm bảo địa chỉ có tất cả các phần bắt buộc của địa chỉ email (người dùng, ký hiệu "@" và tên miền), sau đó xác minh rằng chính tên miền tồn tại.

Không có cách nào để xác định (phía máy chủ) nếu người dùng email tồn tại cho một tên miền bên ngoài.

Đây là một phương thức tôi đã tạo trong một lớp Utility:

public static function validateEmail($email)
{
    // SET INITIAL RETURN VARIABLES

        $emailIsValid = FALSE;

    // MAKE SURE AN EMPTY STRING WASN'T PASSED

        if (!empty($email))
        {
            // GET EMAIL PARTS

                $domain = ltrim(stristr($email, '@'), '@') . '.';
                $user   = stristr($email, '@', TRUE);

            // VALIDATE EMAIL ADDRESS

                if
                (
                    !empty($user) &&
                    !empty($domain) &&
                    checkdnsrr($domain)
                )
                {$emailIsValid = TRUE;}
        }

    // RETURN RESULT

        return $emailIsValid;
}

Neverbounce tuyên bố API của họ có thể xác thực phân phối tới 97%. Tất nhiên, miễn là bạn không ngại bàn giao cơ sở dữ liệu danh bạ của mình.
Tom Russell

stristrsẽ không nhận được tên miền nếu có nhiều dấu @. Tốt hơn explode('@',$email)và kiểm tra xemsizeof($array)==2
Aaron Gillion

@AaronGillion Mặc dù bạn đúng như cách tốt hơn để có được các phần miền, phương thức vẫn trả về false như checkdnsrr()trả về false nếu có dấu @ trong tên miền.
Jabari

11

Tôi nghĩ rằng bạn có thể tốt hơn khi sử dụng các bộ lọc sẵn có của PHP - trong trường hợp cụ thể này:

Nó có thể trả về đúng hoặc sai khi được cung cấp với FILTER_VALIDATE_EMAILparam.


9

Điều này sẽ không chỉ xác nhận email của bạn, mà còn vệ sinh nó cho các ký tự không mong muốn:

$email  = $_POST['email'];
$emailB = filter_var($email, FILTER_SANITIZE_EMAIL);

if (filter_var($emailB, FILTER_VALIDATE_EMAIL) === false ||
    $emailB != $email
) {
    echo "This email adress isn't valid!";
    exit(0);
}

4

Đã trả lời câu hỏi này trong 'câu hỏi hàng đầu' về xác minh email https://stackoverflow.com/a/41129750/1848217

Đối với tôi cách đúng để kiểm tra email là:

  1. Kiểm tra biểu tượng @ tồn tại và trước và sau nó có một số biểu tượng không @: /^[^@]+@[^@]+$/
  2. Cố gắng gửi email đến địa chỉ này với một số "mã kích hoạt".
  3. Khi người dùng "kích hoạt" địa chỉ email của mình, chúng ta sẽ thấy rằng tất cả đều đúng.

Tất nhiên, bạn có thể hiển thị một số cảnh báo hoặc chú giải công cụ ở mặt trước khi người dùng nhập email "lạ" để giúp anh ta tránh các lỗi phổ biến, như không có dấu chấm trong phần tên miền hoặc dấu cách trong tên mà không trích dẫn, v.v. Nhưng bạn phải chấp nhận địa chỉ "hello @ world" nếu người dùng thực sự muốn nó.

Ngoài ra, bạn phải nhớ rằng tiêu chuẩn địa chỉ email đã và có thể phát triển, vì vậy bạn không thể chỉ cần nhập một số regrec "hợp lệ tiêu chuẩn" một lần và mãi mãi. Và bạn phải nhớ rằng một số máy chủ internet cụ thể có thể thất bại một số chi tiết về tiêu chuẩn chung và trên thực tế hoạt động với "tiêu chuẩn được sửa đổi".

Vì vậy, chỉ cần kiểm tra @, gợi ý người dùng ở lối vào và gửi email xác minh theo địa chỉ đã cho.


1
Regex của bạn không kiểm tra @, nhưng nó không thực sự kiểm tra xem nó có hợp lệ đối với bất kỳ RFC nào chi phối email không. Nó cũng không hoạt động như văn bản. Tôi đã chạy nó qua regex101.com và nó không khớp với các địa chỉ hợp lệ
Machavity

Bạn chỉ đọc regex hoặc toàn bộ câu trả lời? Hoàn toàn không đồng ý với bạn. Vui lòng chỉ cho tôi biết, theo RFC, máy chủ gmail giả định rằng joe @ gmail và jo.e @ gmail là cùng một địa chỉ? Có rất nhiều máy chủ hoạt động không theo tiêu chuẩn hoặc không theo tiêu chuẩn TƯƠI. Nhưng thay vào đó phục vụ email của người dùng của họ. Nếu bạn nhập một số biểu thức chính quy một lần và chỉ xác thực bằng cách đó, bạn không có gì đảm bảo rằng nó sẽ giữ đúng trong tương lai và người dùng trong tương lai của bạn sẽ không thất bại với email "cách mới" của họ. Vì vậy, vị trí của tôi là như nhau: điểm chính nếu bạn muốn xác minh địa chỉ email - chỉ cần gửi email kích hoạt.
FlameStorm

@Machavity nhưng cảm ơn vì bugreport trong regapi, tôi đã sửa nó từ /^[^@]+@[^@+]$/thành/^[^@]+@[^@]+$/
FlameStorm

Đạo cụ cho bạn để sửa regex, nhưng làm thế nào để cải thiện filter_varphương thức này? Nó cũng không khắc phục vấn đề chấp nhận các địa chỉ được định dạng sai. Regex của bạn sẽ vui vẻ chấp nhận joe@domainlàm địa chỉ email hợp lệ, khi đó không phải là
Machavity

@Machavity, chẳng hạn, có một phiên bản cụ thể của PHP trên máy chủ của bạn và bạn không thể cập nhật nó lên bản mới nhất. Ví dụ: bạn có php 5.5.15. Năm 2018 tiêu chuẩn của email hợp lệ đã được mở rộng. Nó sẽ nhận ra trong php 7.3.10 sớm. Và sẽ có chức năng làm việc tốt filter_var($email, FILTER_VALIDATE_EMAIL, $newOptions). Nhưng bạn có chức năng cũ trên máy chủ, bạn không thể cập nhật trong một số trường hợp. Và bạn sẽ mất khách hàng với một số email hợp lệ mới. Ngoài ra, một lần nữa tôi nhận thấy rằng không phải tất cả các máy chủ phục vụ email đều hoạt động theo đúng tiêu chuẩn phổ biến và hiện đại của địa chỉ email.
FlameStorm

3

Nếu bạn muốn kiểm tra xem tên miền được cung cấp từ địa chỉ email có hợp lệ hay không, hãy sử dụng một cái gì đó như:

/*
* Check for valid MX record for given email domain
*/
if(!function_exists('check_email_domain')){
    function check_email_domain($email) {
        //Get host name from email and check if it is valid
        $email_host = explode("@", $email);     
        //Add a dot to the end of the host name to make a fully qualified domain name and get last array element because an escaped @ is allowed in the local part (RFC 5322)
        $host = end($email_host) . "."; 
        //Convert to ascii (http://us.php.net/manual/en/function.idn-to-ascii.php)
        return checkdnsrr(idn_to_ascii($host), "MX"); //(bool)       
    }
}

Đây là cách thuận tiện để lọc nhiều địa chỉ email không hợp lệ, cùng với xác thực email độc lập, vì định dạng email hợp lệ không có nghĩa là email hợp lệ .

Lưu ý rằng idn_to_ascii()chức năng (hoặc chức năng chị em của mình idn_to_utf8()) có thể không sẵn trong cài đặt PHP của bạn, nó yêu cầu các phần mở rộng PECL intl> = 1.0.2 và PECL idn> = 0.1.

Ngoài ra, hãy nhớ rằng IPv4 hoặc IPv6 là một phần tên miền trong email (ví dụ user@[IPv6:2001:db8::1]) không thể được xác thực, chỉ được đặt tên máy chủ có có thể.

Xem thêm tại đây .


Tôi không nghĩ nó sẽ hoạt động nếu phần lưu trữ của địa chỉ email ở địa chỉ IP ở định dạng IPv6
GordonM

2

Sau khi đọc câu trả lời ở đây, đây là những gì tôi đã kết thúc với:

public static function isValidEmail(string $email) : bool
{
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return false;
    }

    //Get host name from email and check if it is valid
    $email_host = array_slice(explode("@", $email), -1)[0];

    // Check if valid IP (v4 or v6). If it is we can't do a DNS lookup
    if (!filter_var($email_host,FILTER_VALIDATE_IP, [
        'flags' => FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE,
    ])) {
        //Add a dot to the end of the host name to make a fully qualified domain name
        // and get last array element because an escaped @ is allowed in the local part (RFC 5322)
        // Then convert to ascii (http://us.php.net/manual/en/function.idn-to-ascii.php)
        $email_host = idn_to_ascii($email_host.'.');

        //Check for MX pointers in DNS (if there are no MX pointers the domain cannot receive emails)
        if (!checkdnsrr($email_host, "MX")) {
            return false;
        }
    }

    return true;
}

1

Nếu bạn chỉ tìm kiếm một regex thực tế cho phép các dấu chấm khác nhau, dấu gạch dưới và dấu gạch ngang, thì như sau : [a-zA-z0-9.-]+\@[a-zA-z0-9.-]+.[a-zA-Z]+. Điều đó sẽ cho phép một email trông khá ngu ngốc muốn tom_anderson.1-neo@my-mail_matrix.comđược xác nhận.


0
/(?![[:alnum:]]|@|-|_|\.)./

Ngày nay, nếu bạn sử dụng biểu mẫu HTML5 type=emailthì bạn đã an toàn tới 80% vì các công cụ trình duyệt có trình xác nhận riêng. Để bổ sung cho nó, hãy thêm regex này vào của bạn preg_match_all()và phủ nhận nó:

if (!preg_match_all("/(?![[:alnum:]]|@|-|_|\.)./",$email)) { .. }

Tìm regex được sử dụng bởi các biểu mẫu HTML5 để xác thực
https://regex101.com/r/mPEKmy/1


Tôi ghét downvote quá w / o giải thích. Chà tôi đoán anh ta có thể nói: Kiểm tra email trình duyệt (phía khách hàng) không an toàn chút nào. Bất cứ ai cũng có thể gửi bất cứ điều gì đến một máy chủ bằng cách thay đổi mã. Vì vậy, đó là cách rõ ràng và an toàn nhất để thực hiện kiểm tra (một lần nữa) phía máy chủ. Câu hỏi ở đây dựa trên PHP, vì vậy rõ ràng Cameron đang tìm kiếm một giải pháp máy chủ chứ không phải cho một giải pháp máy khách.
Jonny

Câu trả lời này có thể không hoàn toàn liên quan đến PHP, nhưng gợi ý HTML bao trùm người dùng "chuẩn" chỉ bằng điện thoại / PC. Ngoài ra, người dùng sẽ nhận được thông tin trực tiếp trong trình duyệt "của mình" trong khi sử dụng trang web. Kiểm tra thực sự ở phía máy chủ không được bảo hiểm với điều này, chắc chắn. Btw, @Thielicy đã đề cập đến một thay đổi PHP, vì vậy bình luận của anh ấy có liên quan đến IMHO.
k00ni

Có lẽ nó đã nhận được số phiếu giảm do giả định rằng bạn "an toàn 80% vì các công cụ trình duyệt có trình xác nhận riêng". Có nhiều cách khác để gửi yêu cầu http hơn thông qua trình duyệt, vì vậy bạn không thể cho rằng mọi yêu cầu đều an toàn ... ngay cả khi bạn kiểm tra tác nhân trình duyệt.
Jabari
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.