最近遇到一个需求:需要验证用户填写的邮箱地址是否真实存在,是否可达。和普通的正则表达式不同,他要求尝试链接目标邮箱服务器并请求校验目标邮箱是否存在。
先来了解
DNS之MX记录
对于DNS不了解的,请移步百度搜索。
DNS中除了A记录(域名-IP映射)之外,还有MX记录(邮件交换记录),CNAME记录(别名,咱不管)。
MX记录就是为了在发送邮件时使用友好域名规则,比如我们发送到QQ邮箱
[email protected]。我们填写地址是到“qq.com”,但实际上可能服务器地址千奇百怪/而且许有多个。在设置DNS时可以顺带设置MX记录。
说白了,“qq.com”只是域名,做HTTP请求响应地址,你邮件能发到Tomcat上吗?那我们发到“qq.com”上面的邮件哪里去了,我们把自己想象成一个邮件服务器,你的用户让你给
[email protected]发一封信,你如何操作?找mx记录是必要的。
SMTP之纯Socket访问
对于SMTP不了解或Java Socket不了解的,请移步百度搜索。
邮件协议是匿名协议,我们通过SMTP协议可以让邮件服务器来验证目标地址是否真实存在。
由以上介绍可知:通过DNS中MX记录可以找到邮件服务器地址,通过SMTP协议可以让邮件服务器验证目标邮箱地址的真实性。
那么我们就来进行编码实现。
首先需要查询DNS,这个需要用到一个Java查询DNS的组件dnsjava(下载),自己写太麻烦。
(上面代码中的生僻类型就是来自dnsjava,我使用apache-commons组件来判断空值和构建排序,return false是在查询失败时。)
接下来通过优先级排序(mx记录有这个属性)取第一个邮件服务器地址来链接。
这里的主要代码是通过SMTP发送RCPT TO指令来指定邮件接收方,如果这个地址存在则服务器返回成功状态,如果没有的话则返回错误指令。
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.MXRecord;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
public class MailValid {
public static void main(String[] args) {
System.out.println(new MailValid().valid(”
[email protected]“, ”
[email protected]“));
}
/**
* 验证邮箱是否存在
* 由于要读取IO,会造成线程阻塞
* @param toMail要验证的邮箱
* @param domain发出验证请求的域名(是当前站点的域名,可以任意指定)
* @return邮箱是否可达
*/
public boolean valid(String toMail, String domain) {
if(StringUtils.isBlank(toMail) || StringUtils.isBlank(domain)) return false;
if(!StringUtils.contains(toMail, ‘@’)) return false;
String host = toMail.substring(toMail.indexOf(‘@’) + 1);
if(host.equals(domain)) return false;
Socket socket = new Socket();
try {
// 查找mx记录
Record[] mxRecords = new Lookup(host, Type.MX).run();
if(ArrayUtils.isEmpty(mxRecords)) return false;
// 邮件服务器地址
String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString();
if(mxRecords.length > 1) { // 优先级排序
List<Record> arrRecords = new ArrayList<Record>();
Collections.addAll(arrRecords, mxRecords);
Collections.sort(arrRecords, new Comparator<Record>() {
public int compare(Record o1, Record o2) {
return new CompareToBuilder().append(((MXRecord)o1).getPriority(), ((MXRecord)o2).getPriority()).toComparison();
}
});
mxHost = ((MXRecord)arrRecords.get(0)).getTarget().toString();
}
// 开始smtp
socket.connect(new InetSocketAddress(mxHost, 25));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(socket.getInputStream())));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// 超时时间(毫秒)
long timeout = 6000;
// 睡眠时间片段(50毫秒)
int sleepSect = 50;
// 连接(服务器是否就绪)
if(getResponseCode(timeout, sleepSect, bufferedReader) != 220) {
return false;
}
// 握手
bufferedWriter.write(“HELO ” + domain + “/r/n”);
bufferedWriter.flush();
if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
return false;
}
// 身份
bufferedWriter.write(“MAIL FROM: <
[email protected]” + domain + “>/r/n”);
bufferedWriter.flush();
if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
return false;
}
// 验证
bufferedWriter.write(“RCPT TO: <” + toMail + “>/r/n”);
bufferedWriter.flush();
if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
return false;
}
// 断开
bufferedWriter.write(“QUIT/r/n”);
bufferedWriter.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private int getResponseCode(long timeout, int sleepSect, BufferedReader br) throws Exception {
int code = 0;
for(long i = sleepSect; i < timeout; i += sleepSect) {
Thread.sleep(sleepSect);
if(bufferedReader.ready()) {
String outline = bufferedReader.readLine();
// FIXME 读完……
while(bufferedReader.ready())
code = Integer.parseInt(outline.substring(0, 3));
break;
}
}
return code;
}
}
(解锁和输出123、124行数据可以让你更加清晰SMTP协议)
对于企业邮箱,可能无法正常验证,这个是因为服务器问题。另外,dnsjava查询DNS是有缓存的。
转载请注明来源网站:blog.ytso.com谢谢!
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/14713.html