Java发送短信验证码

要求

  • 生成6位随机验证码
  • 向第三方接口发送手机号和验证码,由第三方接口发送验证码到指定手机
  • 将验证码和发送时间存入session,供controller层比对验证码是否正确和有效

简介

  • 发送短信需要借助第三方接口,目前提供短信服务的第三方平台有很多,首选秒嘀科技,注册即送10元约200条免费试用优惠,但需要企业认证才能发送短信。阿里云也提供短信服务,但必须充值才能使用。

注册秒嘀科技

  • 访问秒嘀科技:http://www.miaodiyun.com/,注册账号
  • 登录,点击用户中心->账户管理,获取ACCOUNT_SID和AUTH_TOKEN(后者需要验证)。
  • 点击配置管理->验证码短信模板->新建模板,填写信息后提交审核,很快能好

术语

  • SMS:Short Message Service,短信服务
  • sig: 一种有效的数字签名,以便在手机设备上运行。
  • sid:Security Identifiers,是用户、组合计算机账户的唯一身份标识。

MessageDigest类

  • 为应用程序提供信息摘要算法,如MD5或SHA算法。信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。
  • MessageDigest.getInstance(String algorithm):返回实现指定摘要算法的MessageDigest对象。
  • update(byte[]):指定要生成摘要的信息,返回值为void。
  • digest():返回计算出来的哈希摘要,是byte[],通常与update方法同用。
  • digest(byte[]):可以不用update方法。
  • isEqual(byte[],byte[]):两个摘要是否相同,做简单的字节比较。

代码

  • 短信发送方面不需要导什么包,因为我们是用网络访问的方式调用第三方接口,只需要程序发起请求,不需要额外引入依赖。

Java发送短信验证码

Java发送短信验证码

  • 发送短信工具类

      /**
       * @Author haien
       * @Description 发送短信服务工具类
       * @Date 2019/2/10
       * @Param
       * @return
       **/
      public class SmsUtil {
          //第三方平台接口
          private static final String QUERY_PATH=
                  "https://api.miaodiyun.com/20150822/industrySMS/sendSMS";
          //我的账号认证
          private static final String ACCOUNT_SID="78789aaee2d54938adb96d01f96dae79";
          private static final String AUTH_TOKEN="1834bbf37ebc457e8141d290127a431d";
      
          public static int sendCode(String phone,String code) 
                  throws NoSuchAlgorithmException, IOException {
              
              //定义该次请求
              URL url=new URL(QUERY_PATH);
              HttpURLConnection connection=(HttpURLConnection)url.openConnection();
              connection.setRequestMethod("POST");
              connection.setDoInput(true); //是否允许数据写入
              connection.setDoOutput(true); //是否允许数据输出
              connection.setConnectTimeout(5000); //链接响应时间
              connection.setReadTimeout(10000); //参数读取时间
              connection.setRequestProperty("Content-type","application/x-www-form-urlencoded");
              
              //提交请求
              String timestamp=new SimpleDateFormat("yyyyMMddHHmmss")
                      .format(new Date());//时间戳
              String sig=getMD5(ACCOUNT_SID,AUTH_TOKEN,timestamp); //签名(用于手机设备认证)
              String tamp="您的验证码为"+code
                      +",请于5分钟内正确输入,如非本人操作,请忽略此短信。"; //短信内容
              String args=getQueryArgs(ACCOUNT_SID,tamp,phone
                      ,timestamp,sig,"JSON"); //请求参数拼接
              OutputStreamWriter out=new OutputStreamWriter(connection.getOutputStream()
                      ,"UTF-8"); 
              out.write(args); //发起请求
              out.flush();
              
              //读取返回参数
              BufferedReader br=new BufferedReader(new InputStreamReader(connection.getInputStream()
                      ,"UTF-8")); //br:{"respCode":"00179","respDesc":"发送短信需要先认证"}
              String temp="";
              StringBuilder result=new StringBuilder();
              while((temp=br.readLine())!=null){ //其实也就一行
                  result.append(temp);
              }
              
              //解析参数,判断成功与否
              JSONObject json=JSONObject.parseObject(result.toString()); //转为json
              String respCode=json.getString("respCode"); //获取respCode对应的值
              String successRespCode="00000"; //成功情况:respCode=00000
              if(successRespCode.equals(respCode))  return 1; //成功
              else return 0; //失败
          }
      
          /**
           * @Author haien
           * @Description 获得sign(签名)
           * @Date 2019/2/10
           * @Param [sid, token, timestamp]
           * @return java.lang.String
           **/
          private static String getMD5(String sid,String token,String timestamp) throws NoSuchAlgorithmException {
              StringBuilder result=new StringBuilder();
              String source=sid+token+timestamp;
              
              //MessageDigest:生成信息摘要,即加密
              MessageDigest digest=MessageDigest.getInstance("MD5"); //指定用MD5算法
              byte[] bytes=digest.digest(source.getBytes()); //要进行加密的信息
              
              for(byte b : bytes){
                  String hex=Integer.toHexString(b & 0xff); //将一个整型转为十六进制
                  if(hex.length()==1) result.append("0"+hex);
                  else    result.append(hex);
              }
              return result.toString();
          }
      
          /**
           * @Author haien
           * @Description 拼接请求参数
           * @Date 2019/2/10
           * @Param [accountSid, smsContent, to, timestamp, sig, respDataType]
           * @return java.lang.String
           **/
          public static String getQueryArgs(String accountSid,String smsContent,String to,
                                            String timestamp, String sig,String respDataType){
              return "accountSid="+accountSid+"&smsContent="+smsContent+"&to="+to+"×tamp="
                      +timestamp+"&timestamp="+timestamp+"&sig="+sig+"&respDataType="
                      +respDataType; //tamp前面是乘号;有的例子没加timestamp参数,会报错的
          }
      }
    
  • 发送结果分析

      发送错误:
      result==>{"respCode":"00134","respDesc":"没有和内容匹配的模板"}
      发送正确:
      result==>{"respCode":"00000","respDesc":"请求成功。",
          "failCount":"0","failList":[],"smsId":"f5d6ec80a8bc4291af8f6d9b7c6555fb"}
    
  • 产生随机验证码工具类

      /**
       * @Author haien
       * @Description 生成随机验证码
       * @Date 2019/2/9
       **/
      public class RandomValidateCodeUtil {
          /**
           * @Author haien
           * @Description 生成6位数字随机验证码
           * @Date 2019/2/10
           * @Param []
           * @return java.lang.String
           **/
          public static String getVerifyCode(){
              return String.valueOf(new Random().nextInt(899999)+100000);
          }
      }
    
  • controller层发送短信

      /**
       * @return void
       * @Author haien
       * @Description 发送短信验证码
       * @Date 2019/2/9
       * @Param [request, response]
       **/
      @RequestMapping("/getVerifyCode")
      public Map<String, String> getVerifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
          Map<String, String> result = new HashMap<>();
          String phone = request.getParameter("phone");
          if (StringUtil.isNull(phone)) {
              result.put("message", "请填写手机号");
              return result;
          }
          if (!ValidatorUtil.isMobile(phone)) {
              result.put("message", "手机格式错误");
              return result;
          }
          if (userService.findUserByPhone(phone) != null) {
              result.put("message", "手机已绑定");
              return result;
          }
          String code = RandomValidateCodeUtil.getVerifyCode();
          logger.info("----请求验证码--code={}----", code);
          try {
              int success = SmsUtil.sendCode(phone, code);
          } catch (NoSuchAlgorithmException e) {
              logger.info("----短信发送失败----");
              throw new NoSuchFieldError("生成信息摘要异常:不存在指定的加密算法");
          }
          logger.info("----短信发送成功----");
          JSONObject json = new JSONObject();
          json.put(VERIFYCODEKEY, code);
          json.put(CODECREATETIMEKEY, System.currentTimeMillis());
          request.getSession().setAttribute(VERIFYMSG, json);
          result.put("message", "短信发送成功");
          return result;
      }
    
  • controller层比对验证码

      /**
       * @Author haien
       * @Description 比对验证码是否正确
       * @Date 2019/2/10
       * @Param [verifyCode]
       * @return boolean
       **/
      @RequestMapping(value = "/isVrfCodeRigth", method = RequestMethod.GET)
      public Map<String,String> isVrfCodeRigth(@RequestParam("verifyCode")String verifyCode
              ,HttpServletRequest request){
          Map<String,String> result=new HashMap<>();
          JSONObject verifyMsg=(JSONObject)request.getSession().getAttribute(VERIFYMSG);
          if(!verifyMsg.getString(VERIFYCODEKEY).equals(verifyCode))
              result.put("message","验证码错误");
          if((System.currentTimeMillis()-verifyMsg.getLong(CODECREATETIMEKEY))>1000*60*5) //5分钟
              result.put("message","验证码过期");
          return result;
      }
    

参考文章

代码实例

  • D:/thz/thz-parent/thz-common/SmsUtil、thz-manager-web/WebPageController