1. 程式人生 > >通過郵件找回密碼功能的實現

通過郵件找回密碼功能的實現

1、最近開發一個系統,有個需求就是,忘記密碼後通過郵箱找回。現在的系統在註冊的時候都會強制輸入郵箱,其一目的就是 通過郵件繫結找回,可以進行密碼找回。通過java傳送郵件的功能我就不說了,重點講找回密碼。

2、參考別人的思路:傳送郵件→請求郵件裡的URL→驗證url→{驗證成功修改密碼,不成功跳轉到失敗頁面}

重點就是如何生成這個url和如何解析這個url.
需要注意的是一個url只能修改一次密碼,當同一帳號傳送多封郵件,只有最後一封郵件的url

3、加密能防止偽造攻擊,一次url只能驗證一次,並且綁定了使用者。生成url: 可以用UUID生成隨機金鑰。

數字簽名 = MD5(使用者名稱+'$'+過期時間+‘$’+金鑰key)


資料庫欄位(使用者名稱(主鍵),金鑰key,過期時間)
url引數(使用者名稱,數字簽名) ,金鑰key的生成:在每一個使用者找回密碼時候為這個使用者生成一個金鑰key ,

生成過期時間,生成數字簽名,生成url,傳送郵件. saveOrUpdate(使用者名稱,金鑰key,過期時間)

以下為springMvc程式碼

    @RequestMapping(value = "/user/i_forget_password")
    @ResponseBody
    public Map forgetPass(HttpServletRequest request,String userName){
        Users users = userService.findUserByName(userName);
        Map map = new HashMap<String ,String >();
        String msg = "";
        if(users == null){              //使用者名稱不存在
            msg = "使用者名稱不存在,你不會忘記使用者名稱了吧?";
            map.put("msg",msg);
            return map;
        }
        try{
            String secretKey= UUID.randomUUID().toString();  //金鑰
            Timestamp outDate = new Timestamp(System.currentTimeMillis()+30*60*1000);//30分鐘後過期
            long date = outDate.getTime()/1000*1000;                  //忽略毫秒數
            users.setValidataCode(secretKey);
            users.setRegisterDate(outDate);
            userService.update(users);    //儲存到資料庫
            String key = users.getUserName()+"$"+date+"$"+secretKey;
            String digitalSignature = MD5.MD5Encode(key);                 //數字簽名

            String emailTitle = "有方雲密碼找回";
            String path = request.getContextPath();
            String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
            String resetPassHref =  basePath+"user/reset_password?sid="+digitalSignature+"&userName="+users.getUserName();
            String emailContent = "請勿回覆本郵件.點選下面的連結,重設密碼<br/><a href="+resetPassHref +" target='_BLANK'>點選我重新設定密碼</a>" +
                    "<br/>tips:本郵件超過30分鐘,連結將會失效,需要重新申請'找回密碼'"+key+"\t"+digitalSignature;
            System.out.print(resetPassHref);
            SendMail.getInstatnce().sendHtmlMail(emailTitle,emailContent,users.getEmail());
            msg = "操作成功,已經發送找回密碼連結到您郵箱。請在30分鐘內重置密碼";
            logInfo(request,userName,"申請找回密碼");
        }catch (Exception e){
            e.printStackTrace();
            msg="郵箱不存在?未知錯誤,聯絡管理員吧。";
        }
        map.put("msg",msg);
        return map;
    }


找回連結已經發到郵箱了。進入郵箱點開連結

以下為連結檢驗程式碼,驗證通過 跳轉到修改密碼介面,否則跳轉到失敗介面

 @RequestMapping(value = "/user/reset_password",method = RequestMethod.GET)
    public ModelAndView checkResetLink(String sid,String userName){
        ModelAndView model = new ModelAndView("error");
        String msg = "";
        if(sid.equals("") || userName.equals("")){
            msg="連結不完整,請重新生成";
            model.addObject("msg",msg) ;
            logInfo(userName,"找回密碼連結失效");
            return model;
        }
        Users users = userService.findUserByName(userName);
        if(users == null){
            msg = "連結錯誤,無法找到匹配使用者,請重新申請找回密碼.";
            model.addObject("msg",msg) ;
            logInfo(userName,"找回密碼連結失效");
            return model;
        }
        Timestamp outDate = users.getRegisterDate();
        if(outDate.getTime() <= System.currentTimeMillis()){         //表示已經過期
            msg = "連結已經過期,請重新申請找回密碼.";
            model.addObject("msg",msg) ;
            logInfo(userName,"找回密碼連結失效");
            return model;
        }
        String key = users.getUserName()+"$"+outDate.getTime()/1000*1000+"$"+users.getValidataCode();          //數字簽名
        String digitalSignature = MD5.MD5Encode(key);
        System.out.println(key+"\t"+digitalSignature);
        if(!digitalSignature.equals(sid)) {
            msg = "連結不正確,是否已經過期了?重新申請吧";
            model.addObject("msg",msg) ;
            logInfo(userName,"找回密碼連結失效");
            return model;
        }
        model.setViewName("user/reset_password");  //返回到修改密碼的介面
        model.addObject("userName",userName);
        return model;
    }

補充1:Timestamp型別物件在儲存到資料的時候 毫秒精度會丟失。比如:2013-10-08 10:29:10.234 存到mysql資料庫的時候 變成 2013-10-08 10:29:10.0。時間變得不相同了,sid 匹配的時候不會相等。 所以我做了忽略精度的操作。

補充2:解決linux下面title中文亂碼

      sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder();
      mailMessage.setSubject(MimeUtility.encodeText(mailInfo.getSubject(), "UTF-8", "B"));      //解決linux郵件title亂碼

補充3:怎麼不直接把sid插入到user表呢。驗證的時候直接比較sid就ok了。