
如果你的注冊(cè)流程需要一個(gè)簡(jiǎn)單的郵箱驗(yàn)證功能,或許我的思考能幫到你
僅僅驗(yàn)證郵箱是否存在?
注冊(cè)時(shí)我們需要保證用戶填寫(xiě)的郵箱信息有效,這時(shí)我們的思路很簡(jiǎn)單,就是想辦法驗(yàn)證郵箱是否真實(shí)存在,我去網(wǎng)上搜了搜,找到了我使用的第一個(gè)方法,使用rcpt to僅僅去驗(yàn)證郵箱是否存在
可以在點(diǎn)擊這里稍微學(xué)習(xí)一下
https://www.activexperts.com/...
從網(wǎng)上找到的部分對(duì)應(yīng)代碼
public Response CheckEmailValidity(@RequestParam (value="email") String email) {
String host = "";
String hostName = email.split("@")[1];
Record[] result = null;
SMTPClient client = new SMTPClient();
Response response = new Response();
try {
// find MX records
Lookup lookup = new Lookup(hostName, Type.MX);
lookup.run();
if (lookup.getResult() != Lookup.SUCCESSFUL) {
response.setCode(ResponseCode.FAIL);
response.setMessage("The email is not exist");
logger.debug(response.getMessage());
return response;
} else {
result = lookup.getAnswers();
}
// connect to email server
for (int i = 0; i < result.length; i++) {
host = result[i].getAdditionalName().toString();
client.connect(host);
if (SMTPReply.isPositiveCompletion(client.getReplyCode())) {
break;
}
client.disconnect();
}
client.login("dashanju");
client.setSender("123456@yahoo.com");
client.addRecipient(email);
if (250 == client.getReplyCode()) {
response.setCode(ResponseCode.SUCCESS);
response.setMessage("The email is exist");
}else{
response.setCode(ResponseCode.FAIL);
response.setMessage("The email is not exist");
logger.debug(response.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
client.disconnect();
} catch (IOException e) {
}
}
return response;
}
不足之處的思考
但是這種方法有一些弊端:
- 我在測(cè)試的時(shí)候發(fā)現(xiàn)部分郵箱驗(yàn)證是有問(wèn)題的,比如123@qq.com,這個(gè)郵箱是不存在的,但是rcpt to的返回結(jié)果是true
- 所以我們不僅需要驗(yàn)證郵箱是否真實(shí)存在,更需要驗(yàn)證這個(gè)郵箱是否是注冊(cè)者本人的郵箱,如果注冊(cè)者隨便填一個(gè)別人的郵箱,那就亂套了
第一個(gè)問(wèn)題,rcpt to確實(shí)無(wú)法保證做到100%的準(zhǔn)確度,對(duì)于這個(gè)問(wèn)題,我們可以靠其他技術(shù)來(lái)解決(JavaMailSender),這里我參考了這篇博客
https://blog.csdn.net/herui_M...
部分常見(jiàn)的網(wǎng)站是如何進(jìn)行郵箱驗(yàn)證的?
在思考第二個(gè)問(wèn)題之前,我們不妨思考一下我們平時(shí)注冊(cè)賬號(hào)的網(wǎng)站對(duì)于郵箱驗(yàn)證的流程是怎么樣的?在我們點(diǎn)擊注冊(cè)按鈕之前,還需要點(diǎn)擊一個(gè)驗(yàn)證郵箱的按鈕,只有自己的郵箱收到郵件,并且自己點(diǎn)擊郵件中的url,才會(huì)出現(xiàn)驗(yàn)證完后注冊(cè)頁(yè)面告訴我們的-“驗(yàn)證成功”
第二個(gè)問(wèn)題的大致思路,后端寫(xiě)三個(gè)接口
sendEmail,用來(lái)發(fā)送郵件
receiveEmail,接收用戶驗(yàn)證信息臨時(shí)存儲(chǔ)到緩存中,
checkEmail,查詢(xún)緩存,驗(yàn)證Email是否已被驗(yàn)證
初期的簡(jiǎn)單流程
整體的流程是這樣的:
- 前端驗(yàn)證郵箱格式后,請(qǐng)求send接口
- 后端send接口發(fā)送驗(yàn)證郵件(郵件中的url中應(yīng)該攜帶email信息,為了避免泄露信息,可以先對(duì)email的信息加密在進(jìn)行拼接)
- 用戶收到郵件,點(diǎn)擊鏈接訪問(wèn)
- 前端拿到加密過(guò)的email信息,去請(qǐng)求receive接口
- receive接口對(duì)加密的email進(jìn)行解密,然后判斷是否為郵箱格式,再存入Redis緩存中
- 前端訪問(wèn)check接口,查詢(xún)Redis緩存中是否有email信息
用戶之間的驗(yàn)證沖突
這樣看似已經(jīng)差不多可以了,但是還有一種情況——a注冊(cè)了b的郵箱
假設(shè)有a,b兩個(gè)用戶,同時(shí)注冊(cè),a填寫(xiě)了b的email信息,b也填寫(xiě)了b的email信息,顯然發(fā)送驗(yàn)證email信息的時(shí)候,這個(gè)郵件只有b真實(shí)能收到,b點(diǎn)了驗(yàn)證鏈接,a的流程卻搶先一步去查詢(xún)了Redis中email是否被驗(yàn)證,我們知道這個(gè)email是b所屬且b本人驗(yàn)證的,而在這個(gè)過(guò)程中a卻反客為主,將郵箱占為己有,所以這里的問(wèn)題就是,我們?nèi)绾伪WC,a申請(qǐng)的郵箱一定是a本人點(diǎn)擊確認(rèn)的,即前端申請(qǐng)email的人和緩存中點(diǎn)擊email確認(rèn)的人是一個(gè)人
進(jìn)一步思考后的整體流程
這個(gè)問(wèn)題的解決方法就是用一個(gè)唯一標(biāo)識(shí)符UUID來(lái)解決,我們對(duì)之前的流程重新思考一下
- 前端驗(yàn)證郵箱格式后,請(qǐng)求send接口
- 后端send接口發(fā)送郵件,郵件中的url應(yīng)該包含加密的email信息和UUID,并且應(yīng)該把UUID返回給前端
- 用戶收到send接口發(fā)來(lái)的郵件,點(diǎn)擊鏈接訪問(wèn)
- 前端收到了加密的email信息和UUID信息,加密的email信息不做處理,對(duì)此時(shí)收到的UUID和請(qǐng)求send接口時(shí)收到的UUID進(jìn)行比較,防止惡意訪問(wèn)(這也是為什么第二步send接口應(yīng)該返回給前端UUID的原因),驗(yàn)證通過(guò)后攜帶加密的email信息和UUID去請(qǐng)求receive接口
- receive接口收到信息后,對(duì)加密的email信息解密和格式驗(yàn)證,然后以鍵值對(duì)的形式將email-UUID存入Redis緩存中
- 前端訪問(wèn)check接口,攜帶email和UUID查詢(xún)緩存中是否存在,然后再去申請(qǐng)注冊(cè)
思考過(guò)程難免會(huì)有疏漏,歡迎指正