要求
- 生成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[]):两个摘要是否相同,做简单的字节比较。
代码
- 短信发送方面不需要导什么包,因为我们是用网络访问的方式调用第三方接口,只需要程序发起请求,不需要额外引入依赖。
发送短信工具类
/** * @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+"×tamp="+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; }
参考文章
- 秒嘀科技注册说明与发送短信代码参考
- 验证码和有效时间在session中的存取操作
代码实例
- D:/thz/thz-parent/thz-common/SmsUtil、thz-manager-web/WebPageController