java發送郵件的兩種通用方法
一、
本文講解的是基於smtp協議,發送郵件的方法(一種是底層實現,一種是利用第三方jar包)。而關於smtp協議,不瞭解的可以在網上搜一下,有很多資料並且很容易懂;不過不了解也沒關係,只需要知道,smtp協議存在一個安全漏洞,就是smtp協議允許你兩次設置發件人和收件人信息。第一次發送命令行mail from:真正的發送郵件的源地址 ;第二次則是在發送data命令之後,開始寫郵件內容。在寫郵件內容時,還能再一次設置發件人、收件人、抄送者等信息(在data裏面寫的發件人、收件人、抄送人信息,只能顯示,其實沒有其他作用,比如你在設置收件人的命令裏面沒有寫[email protected]這個郵件地址,但是你在data命令之後,抄送者裏面輸入了[email protected]這個地址,最後這封郵件並不會發給這個抄送人,只是在郵件的抄送者這一欄裏面,有這麼一個郵箱賬號。所以要真的發送給這些人,只有在最開始設置發件源之後,設置收件源,可以多個)。
順便說一下筆者最開始寫郵件在網上遇到的大坑:筆者寫郵件的背景是,利用公司郵箱公共賬號(比如公共賬號名字是public),將一封郵件發送抄送給一些人,但是要求發件人不能是公共賬號,因爲一些員工設置的郵件過濾,可能會導致用公共賬號名字發送的郵件被直接扔垃圾箱,導致員工看不到郵件,但是利用公共賬號發送的郵件,對方接收的時候顯示的就是公共賬號的名字,即public(PS:修改郵件發件人暱稱,並不能修改接收方看到的發件人名字,暱稱只提供在郵件正文裏面,實際上郵箱顯示的發件人還是公共賬號的名字,比如你修改發件人暱稱爲test,其實對方收到的提醒還是public發送的郵件,並不是test發送的郵件,只有對方點開這封郵件,纔會在郵件裏面看到test這個暱稱。),而且,可能是筆者自己的原因,網上那些利用javaemail包,設置暱稱的辦法(就是這種:InternetAddress senderEmailAddress = new InternetAddress(nick + "<[email protected]>")),筆者這裏根本不管用,最後看了很多源碼之後,終於把暱稱設置好了(這種方法message.setHeader("Sender", "我是暱稱")),結果卻發現,設置的暱稱根本不能僞造發件人,當時筆者心裏的非常崩潰的(尼瑪,搞了半天,好不容易搞定了暱稱,居然發現沒有起到想要的效果,最後筆者只有瞭解smtp協議,然後用Java進行底層實現),所以,筆者要告訴大家的是,使用java封裝好的第三方jar包java發送郵件,不能僞造發件人,不能僞造,不能僞造,重要的事說三遍,詳細的情況在後面會貼一部分源碼講解。
二、基於smtp協議發送郵件(該方法能夠僞造任意發件人)
package sora.test.exampl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
public class SendEmailServiceImpl
{
private String userName;
private String password;
private String host;
private Socket socket;
private BufferedReader bufferedReader;
private PrintWriter printWriter;
public void sendEmailByJavaToUseSmtp(String sender, String reciver, String ccs)
{
try
{
this.socket = new Socket(host, 25);
this.getReader(socket);
this.getWriter(socket);
writeCommandStream(null);
//按照命令行發送郵件的順序與smtp服務器進行交流
writeCommandStream("helo hello");//與smtp服務器進行對話
writeCommandStream("auth login");//登錄命令
writeCommandStream(userName);//登錄用戶用戶名
writeCommandStream(password);//密碼
//登錄成功之後,設置發件人
writeCommandStream("mail from:<" + "xxxxxxxx" + ">");//設置發件人,xxxxxx爲真實的郵件發送源地址,如[email protected]這種郵箱地址
//設置收件人,可以設置多個,所以採用遍歷方式進行設置
//參數reciver裏面裝了所有收件人的郵箱地址,多個郵箱用","號分隔,所以我用逗號拆分
for (String oneReciver : reciver.split(","))
{
writeCommandStream("rcpt to:" + oneReciver);
}
//開始輸入郵件內容
writeCommandStream("data");//郵件內容,在輸入命令data之後開始
//這個地方就是僞造郵件發件人的時候,from之後的字符串任意填,
//填了之後,收到郵件的人,會看到以這個名字發送的郵件,但是他不能回覆,因爲這個是僞造的地址,無效的。
printWriter.println("from:" + "僞造的郵件發件人");
//收件人,格式和抄送者一樣
printWriter.println("to:" + reciver);
//這是抄送者,同收件人一樣,可以設置多個,中間用,號分隔
//比如:[email protected],[email protected],[email protected]
printWriter.println("Cc:" + ccs);
//設置郵件主題
printWriter.println("subject:" + "這是郵件主題");
//設置郵件正文
//注意下面這個設置類型的,這一句代碼是必須的,不然你發的郵件的正文內容是不會存在的
//筆者最開始沒有設置郵件正文類型,發了很多封,但是每一封郵件的正文內容都爲空,後來才發現必須加上這個
printWriter.println("Content-Type:text/html;");//這個是HTML格式的郵件正文,如果是純文本,用text/plain
printWriter.println();
printWriter.println("<span>這是郵件的內容,該郵件是一封HTML格式的郵件,如果要切換郵件格式,"
+ "設置conten-type的值就可以改變,當然還可以加上超鏈接<a href=\"xxxx\">這是超鏈接</a></span>");
printWriter.println();
//結束郵件發送"."命令
writeCommandStream(".");
//關閉
writeCommandStream("quit");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
try
{
printWriter.close();
bufferedReader.close();
socket.close();
}
catch (Exception e2)
{
e2.printStackTrace();
}
}
}
private PrintWriter getWriter(Socket socket) throws IOException
{
OutputStream socketOut = socket.getOutputStream();
return new PrintWriter(socketOut, true); //注意設置爲true
}
private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
private void writeCommandStream(String command) throws IOException
{
if (command != null)
{
printWriter.println(command);
printWriter.flush();
System.out.println("客戶端命令行信息→" + command);
}
char[] serviceResponse = new char[1024];
bufferedReader.read(serviceResponse);
System.out.println("服務器響應→" + new String(serviceResponse));
}
}
三、基於javax.mail包進行郵件發送
就筆者而言,利用該jar包進行郵件發送,沒有真正實現僞造發件人,只能設置郵件發件人暱稱,之前看網上很多僞造都是設置郵件服務器屬性smtp.auth爲false,意思就是不對郵件進行用戶驗證等操作。筆者在設置之後,發送郵件只會提示,作爲該發送者沒有權限,或者xxxxx權限驗證失敗等提示。
另外關於設置暱稱,網上這種方法其實是不能設置暱稱的(也可能是筆者太垃圾,這裏只是代表我個人看法,說不定以後我自己也會發現是錯,現在就講講當時我看源碼的理解,因爲資源原因,源碼以後會陸續貼上)
public void sendEmailByJar(String sender, String recvier, String cc)
{
//設置郵件服務器參數
Properties props = new Properties();
props.put("mail.smtp.host", host);
props.put("mail.smtp.auth", "true");
props.put("mail.transport.protocol", "smtp");
//設置郵件Session對象,同時配置驗證方法
//注意這裏的Session是javax.mail.session包的Session,利用該Jar包,這個Session是必須的,
//關於郵件的一切信息,都是通過這個session進行創建的
Session session = Session.getInstance(props, new javax.mail.Authenticator()
{
protected PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication(userName, password);
}
});
//網上大多數設置暱稱的方法,至少筆者使用該方法不管用
String nick = null;
try
{
nick = javax.mail.internet.MimeUtility.encodeText("我是暱稱");
}
catch (Exception e)
{
e.printStackTrace();
}
try
{
//創建Message對象,並設置相關參數
InternetAddress senderEmailAddress = new InternetAddress(nick + "<xxxx>");
//設置抄送者,cc參數裏面是多個郵箱,用,號分隔
@SuppressWarnings("static-access")
InternetAddress[] ccsAddress = new InternetAddress().parse(cc);
@SuppressWarnings("static-access")
InternetAddress[] reciverAddress = new InternetAddress().parse(recvier);
Message message = new MimeMessage(session);
//筆者親測設置郵件發件人暱稱的方法,至少筆者設置成功
//順便講一下Message對象裏面的header屬性,筆者調試的時候,發現Message對象header屬性保存了我們寫的郵件的所有信息
//裏面有from,sender,to,cc,subject,content-type(包括resent-to,resent-from等,好像是重發郵件的屬性)等屬性,目測就是對應郵件的各個信息
//所以,其實郵件的所有信息,我們都可以通過messaget.setHeader("鍵", "值")來設置
//比如我們調用的設置郵件發件地址的方法setFrom(xxxxx),其實等同於setHeader("From", "xxxxx"),
//如果你同時使用了倆個方法setFrom,setHeader("From", "xxx"),那麼後一個會覆蓋前一個的值
//這裏講一下我理解的爲什麼網上設置暱稱的方法不起作用的原因:網上設置的暱稱都是在setFrom()方法裏面設置的
//而閱讀源碼,我們會發現,setFrom裏面的值,會被拆分到倆個字段裏面保存:personal字段和address字段
//其中,你設置的nick暱稱就會被保存在personnal字段,而郵箱地址會被保存在address字段
//同時,你在源碼裏面也能找到smtp協議的命令行語句mail from這些命令
//源碼裏面,我只看到了這些必要的命令行:發件人mail from ,接收者rcpt to,正文data,結束.
//其中,data源碼是用一個流寫入的,所以具體寫的,怎麼解析的我們設置的參數我也沒看懂,但是實驗證明就是不能僞造發件人
//而mail from,設置的參數的值,是從address字段取的,並沒有取你設置的暱稱personnal,所以直接設置暱稱在from這個header的值是無效的
//rcpt to是從你的收件人裏面取的值。
//而筆者成功的暱稱設置,是通過設置setHeader("Sender", "xxx")成功的,所以可以猜測,源碼解析的時候,取暱稱是從這個字段sender裏面取的
//那麼其實最後jar包源碼裏面,設置smtp mail from還是設置的郵箱,並沒有帶上你設置的暱稱
//所以筆者認爲這個就是使用網上方法設置暱稱不管用的原因(筆者的觀點,可能會有錯,畢竟筆者源碼也沒有完全看懂)
//另外,setFrom()設置的值,必須和登錄驗證用的用戶名和密碼的賬號匹配,不然就會報權限驗證錯誤,所以這也是筆者認爲不能僞造的根本原因
message.setHeader("Sender", "nick");
message.setFrom(senderEmailAddress);//該方法等同於message.setHeader("From","xxx");
message.setRecipients(Message.RecipientType.CC, ccsAddress);
message.setRecipients(Message.RecipientType.TO, reciverAddress);
message.setSubject("主題");
message.setText("簡單文本郵件");
//不管是調用Transport靜態方法send,還是通過session獲取transport,在鏈接,在發送,其實都一樣,源碼已經幫我們處理好了
//如果調用靜態方法,源碼會獲取session對象並用session創建一個transport,如果獲取到session對象爲null,會創建一個默認的session對象
Transport.send(message);
}
catch (Exception e)
{
e.printStackTrace();
}
}
這就是筆者總結的兩種java實現發郵件的方法了,希望對大家有所幫助,如有錯誤,望提醒!!!