java发送邮件的两种通用方法

一、

本文讲解的是基于smtp协议,发送邮件的方法(一种是底层实现,一种是利用第三方jar包)。而关于smtp协议,不了解的可以在网上搜一下,有很多资料并且很容易懂;不过不了解也没关系,只需要知道,smtp协议存在一个安全漏洞,就是smtp协议允许你两次设置发件人和收件人信息。第一次发送命令行mail from:真正的发送邮件的源地址 ;第二次则是在发送data命令之后,开始写邮件内容。在写邮件内容时,还能再一次设置发件人、收件人、抄送者等信息(在data里面写的发件人、收件人、抄送人信息,只能显示,其实没有其他作用,比如你在设置收件人的命令里面没有写123456@qq.com这个邮件地址,但是你在data命令之后,抄送者里面输入了123456@qq.com这个地址,最后这封邮件并不会发给这个抄送人,只是在邮件的抄送者这一栏里面,有这么一个邮箱账号。所以要真的发送给这些人,只有在最开始设置发件源之后,设置收件源,可以多个)。

顺便说一下笔者最开始写邮件在网上遇到的大坑:笔者写邮件的背景是,利用公司邮箱公共账号(比如公共账号名字是public),将一封邮件发送抄送给一些人,但是要求发件人不能是公共账号,因为一些员工设置的邮件过滤,可能会导致用公共账号名字发送的邮件被直接扔垃圾箱,导致员工看不到邮件,但是利用公共账号发送的邮件,对方接收的时候显示的就是公共账号的名字,即public(PS:修改邮件发件人昵称,并不能修改接收方看到的发件人名字,昵称只提供在邮件正文里面,实际上邮箱显示的发件人还是公共账号的名字,比如你修改发件人昵称为test,其实对方收到的提醒还是public发送的邮件,并不是test发送的邮件,只有对方点开这封邮件,才会在邮件里面看到test这个昵称。),而且,可能是笔者自己的原因,网上那些利用javax.mail包,设置昵称的办法(就是这种:InternetAddress senderEmailAddress = new InternetAddress(nick + "<xxxxx@qq.com>")),笔者这里根本不管用,最后看了很多源码之后,终于把昵称设置好了(这种方法message.setHeader("Sender", "我是昵称")),结果却发现,设置的昵称根本不能伪造发件人,当时笔者心里是非常崩溃的(尼玛,搞了半天,好不容易搞定了昵称,居然发现没有起到想要的效果,最后笔者只有了解smtp协议,然后用Java进行底层实现),所以,笔者要告诉大家的是,使用java封装好的第三方jar包发送邮件,不能伪造发件人,不能伪造,不能伪造,重要的事说三遍,详细的情况在后面会贴一部分源码讲解。

二、基于smtp协议发送邮件(该方法能够伪造任意发件人)

package cn.su.core.util;import java.io.*;
import java.net.Socket;
import java.util.Base64;/*** @Author: su rui* @Date: 2021/6/15 10:36* @Description: 伪造邮件发件人工具类*/
public class ForgeEmailSenderUtil {private static final String defaultHost = "smtp.exmail.qq.com";private static final int defaultPort = 25;private String host = "";private Integer port = null;private String userName = "test@qq.com";private String password = "xxxxx";private Socket socket;private BufferedReader bufferedReader;private PrintWriter printWriter;public ForgeEmailSenderUtil setSenderAccount(String userName, String passwordOrAuthCode) {this.userName = userName;this.password = passwordOrAuthCode;return this;}public ForgeEmailSenderUtil setEmailHostAndPort(String host, int port) {this.host = host;this.port = port;return this;}private Socket createSocket() {try {return null == host || host.trim().length() == 0 ? new Socket(defaultHost, defaultPort) : new Socket(host, port);} catch (Exception e) {throw new IllegalArgumentException("创建会话失败,请稍后重试");}}public void sendForgeSenderEmail(String sender, String recipient, String ccs) {sender = null == sender || sender.trim().length() == 0 ? userName : sender;try {String baseUserName = Base64.getEncoder().encodeToString(userName.getBytes("UTF-8"));String basePassword = Base64.getEncoder().encodeToString(password.getBytes("UTF-8"));this.socket = createSocket();this.bufferedReader = getReader(socket);this.printWriter = getWriter(socket);writeCommandStream(null);//按照命令行发送邮件的顺序与smtp服务器进行交流writeCommandStream("helo hello");//与smtp服务器进行对话writeCommandStream("auth login");//登录命令//用户名和密码都是用base64进行编码了的,不是普通的字符串writeCommandStream(baseUserName);//登录用户用户名writeCommandStream(basePassword);//密码//登录成功之后,设置发件人writeCommandStream("mail from:<" + userName + ">");//设置发件人,xxxxxx为真实的邮件发送源地址,如xxx@qq.com这种邮箱地址//设置收件人,可以设置多个,所以采用遍历方式进行设置//参数reciver里面装了所有收件人的邮箱地址,多个邮箱用","号分隔,所以我用逗号拆分for (String oneReciver : recipient.split(",")) {writeCommandStream("rcpt to:" + oneReciver);}//开始输入邮件内容writeCommandStream("data");//邮件内容,在输入命令data之后开始//这个地方就是伪造邮件发件人的时候,from之后的字符串任意填,//填了之后,收到邮件的人,会看到以这个名字发送的邮件,但是他不能回复,因为这个是伪造的地址,无效的。printWriter.println("from:" + sender);//收件人,格式和抄送者一样printWriter.println("to:" + recipient);//这是抄送者,同收件人一样,可以设置多个,中间用,号分隔//比如:xxx@qq.com,xxxxxx@qq.com,xxxx@qq.comprintWriter.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实现发邮件的方法了,希望对大家有所帮助,如有错误,望提醒!!!

java发送邮件的两种实现方式(包括如何伪造发件人及其原理)相关推荐

  1. java动态代理两种实现方式

    代理顾名思义就是代理别人完成某件任务,比如张三代理李四去交物业费.张三就是代理人,李四就是被代理人. Java代理实现,有静态代理和动态代理,静态代理就是代码在编译成class文件后,就已经有代理类的 ...

  2. Java线程的两种实现方式

    前言 线程是程序的一条执行线索,执行路径,是程序使用cpu的最小单位.线程本身不能运行,它只能运行在程序中,线程是依赖于程序存在的. 多线程的意义 其实任何一个程序的执行都需要获得cpu的执行权,是由 ...

  3. java中的uuid是啥,UUID在Java中的两种发作方式

    需求: 集群环境下需要产生uuid,uuid的作用不言而喻 转发请注明出处:http://snv.iteye.com/ 环境: org.safehaus.jug jug 2.0.0 asl 或: or ...

  4. java之Map对象转java对象的两种简单方式

    我们在处理数据的时候,经常用到map对象转java对象,下面我们用代码演示下,希望能够帮助到有这方面需求的老哥. 要转换的java对象: public class Person {private In ...

  5. java定时任务的两种实现方式

    一.Timer 现在项目中用到需要定时去检查文件是否更新的功能.timer正好用于此处. 用法很简单,new一个timer,然后写一个timertask的子类即可. package comz.auto ...

  6. 初始化一个java空数组_Java 数组的两种初始化方式

    一.数组 1.数组中存储元素的类型是统一的,每一个元素在内存中所占用的空间大小是相同的,知道数组的首元素的内存地址,要查找的元素只要知道下标,就可以快速的计算出偏移量,通过首元素内存地址加上偏移量,就 ...

  7. Java两种排序方式快慢比较

    2019独角兽企业重金招聘Python工程师标准>>> Java中List的排序方式有两种,现在我们测试下这两种排序方式的快慢吧,我们需要用到两个类, 一个是运行程序的Main类,另 ...

  8. java 同步方式 lock_java的两种同步方式, Synchronized与ReentrantLock的区别

    java在编写多线程程序时,为了保证线程安全,需要对数据同步,经常用到两种同步方式就是Synchronized和重入锁ReentrantLock. 相似点: 这两种同步方式有很多相似之处,它们都是加锁 ...

  9. Java多线程两种实现方式的对比

    Java多线程两种实现方式的对比 一种,直接继承Thread类 一种,实现Thread类的Runnable接口 两种方式的区别 比如,售票厅有四个窗口,可以发售某日某次列出的100张车票,此时,100 ...

  10. Java技术分享:升级所安装Java版本的两种方式

    在进行Java开发的时候我们可能会需要升级所安装的Java版本,那么你知道应该如何安装吗?小千今天就来给大家介绍两种方式. 一.卸载掉原本安装的Java,下载最新安装包安装即可. 这个步骤就不介绍了, ...

最新文章

  1. 一旦一个业务可以由一个人来全部完成而不涉及分工,就会产生单干的情况
  2. PyQt4编程之如何让状态栏显示信息
  3. 从零开始来看一下Java泛型的设计
  4. JAVA和javascrito_JAVA 和JavaScript的split方法异同
  5. asp 连接mysql_如何在ASP中连接MySQL数据库
  6. CodeForces - 798B Mike and strings
  7. 有道单词本导出xml转换.
  8. autoware中pure_pursuit控制和MPC控制解析
  9. R语言数据统计1——正态性检验
  10. 制作u盘winpe启动盘_如何制作U盘启动盘
  11. 2.某公司要开发新游戏,请用面向对象的思想,设计游戏中的蛇怪和蜈蚣精设定⦁蛇怪类:属性包括:怪物名字,生命值,攻击力方法包括:攻击,移动(曲线移动),补血(当生命值<10时,可以补加20生命值
  12. 在Mac上使用android studio调试android手机
  13. 设计模式(一)设计模式的分类与区别
  14. 2023年重庆邮电大学计算机科学与技术(802)初试经验贴
  15. 小白要努力之为了蓝桥杯刷题!!!超简单哦!!!
  16. Kerberos学习(四)
  17. java基础代码,适合0基础学习者
  18. 销售crm系统排行?2022年终十大销售管理系统软件推荐
  19. matlab增强图像饱和度,matlab增强图像饱和度
  20. 基于Plupload的图片压缩上传

热门文章

  1. Java设计模式pdf
  2. Windows系统下的socket编程
  3. web用css做网页实验报告,Web实验报告网页设计与制作
  4. 专为Oracle数据库恢复而生 - PRM
  5. 如何在MAC上使用VOSviewer和Pajek
  6. 【心里效应】98 个著名的心理效应
  7. 微信经典飞机大战素材
  8. 导航一体机端口测试软件,车载GPS检测端口和屏幕分辨率的软件:PADTOOL
  9. Java集合源码剖析
  10. 软件开发过程模型综述