web-dev-qa-db-ja.com

Javaでパスワードを忘れた場合の機能を実装する

現在、Javaプロジェクトでパスワードを忘れた場合の機能を実装しています。私の方法論は、

  1. ユーザーがパスワードを忘れた場合のリンクをクリックします。
  2. パスワードを忘れた場合のページで、システムはユーザーがシステムに登録したメールアドレスを入力するように促します。
  3. 上記の手順で、パスワードをリセットするためのリンクを含む電子メールが指定の電子メールアドレスに送信されます。
  4. ユーザーがリンクをクリックすると、新しいパスワードを入力できるページ(パスワードのリセット)にリダイレクトされます。
  5. [パスワードのリセット]ページでは、[メールアドレス]フィールドに自動的に入力され、変更できません。
  6. 次に、ユーザーが新しいパスワードを入力すると、データベース内の電子メールアドレスに関連するフィールドが更新されます。

パスワードのリセットページのemail addressフィールドの編集(読み取り専用フィールド)を制限していますが、ブラウザのアドレスバーのURLを変更して、メールアドレスフィールドを変更できます。

すべてのユーザーがパスワードのリセットページでメールアドレスを変更できないように制限するにはどうすればよいですか?

16
vigamage

トークンを使用してメールを送信する前に、DBに保存する必要があります。

  1. ユーザーが[リセットの手順を記載したメールを送信]をクリックすると、DBに次のフィールドを持つ1つのレコードが作成されます:emailtokenexpirationdate
  2. ユーザーはyourwwebsite.com/tokenでメールを受信して​​クリックします
  3. Urlにtokenを使用すると、サーバーはidentify the user、有効期限のおかげでリクエストの有効期限が切れていないか確認し、正しいメールをボックスに入れて、パスワードの更新を要求します。ユーザーが新しいパスワードを入力すると、トークン(hidden fieldの形式)+サーバーへのパスワード。サーバーはメールのテキストボックスを気にしません。なぜならwith the token, user is identified strongly
  4. 次に、サーバーは、トークンがexpirationdate(再度)で有効かどうかを確認し、password matchそして大丈夫なら、新しいパスワードを保存してください!サーバーは、リクエストによりパスワードが変更されたことをユーザーに通知するために、メッセージを再送信できます。

これは本当に安全です。 expirationdateに短い時間を使用してセキュリティを改善し(たとえば5分が正しい)、強力なトークンを使用してください(GUIDとして、コメントを参照)

36
clement

パスワードを忘れた場合の機能を自分で実装する必要がある場合は、@ clementの回答に同意します。この実装の合理的で安全な方法のように思えます。

ただし、代わりに、自分で実装する必要がない場合は、 Stormpath のような、これを行うサービスを使用することをお勧めします。

Stormpathを使用することにした場合、機能をトリガーするコードは、Java(StormpathのJava SDK)で次のようになります。

Account account = application.sendPasswordResetEmail("[email protected]");

ユーザーには次のようなリンクが記載されたメールが届きます。

http://yoursite.com/path/to/reset/page?sptoken=$TOKEN

そして、ユーザーがリンクをクリックすると、次のようにパスワードを確認してリセットします。

Account account = application.resetPassword("$TOKEN", "newPassword");

この仕組みの詳細については、Stormpathの パスワードリセットドキュメント をご覧ください。

このアプローチを使用すると、実装しないオプションがある場合、機能を実装および維持する必要はありません。

注:Stormpathは Okta に参加しました。

5
ecrisostomo

メールアドレスをユーザーによる変更に制限することはできません。
非表示にしたり、テキストボックスを読み取り専用にした場合でも、ブラウザでソースコードを編集することで、メールアドレスを簡単に変更できます。

uniq random string or tokenリセットリンクを使用し、パスワードのリセットリンクをクリックした後、またはユーザーがデータベース内の電子メールアドレスとトークン文字列でリクエストの電子メールアドレスとトークン文字列を確認してリセットパスワードのリクエストを送信した後、電子メールアドレスとトークンの組み合わせを確認します。

データベースに電子メールアドレスが存在する場合、それは電子メールアドレスが有効であることを意味し、そうでない場合は、ユーザーレコードに電子メールアドレスが存在しないというメッセージを伝えます。

注:
フレームワークまたは単にサーブレットを使用している場合は、リンクを提供するよりも良いので、パスワードのリセットフォームを表示する前にメールとトークン文字列を検証できます。トークン文字列または電子メールアドレスが無効な場合、ユーザーはパスワードのリセット要求の送信を制限し、要求の送信後に検証できます。パスワードのリセット要求を送信した後の検証よりも安全です。

3
Yagnesh Agola

この質問は、この回答の3年前に投稿されました...しかし、私はそれが他の人に役立つかもしれないと思います。

要するに、私はあなたの流れに完全に同意します。非常に安全に見えますが、あなたの唯一のオープンエンドも理にかなっています-誰もユーザー名を変更しないことを確認し、それによって彼に新しいパスワードを設定できます。

(受け入れられた答えが示唆するように)一時的に物事を保存するという考え方はDBほど好きではありません。

私が考えていたアイデアは、ユーザーに送信されるリンクのデータに署名することでした。次に、ユーザーがリンクをクリックしてサーバーが呼び出しを受信すると、サーバーも暗号化された部分を取得し、データが変更されていないことを検証できます。

ちなみに(ここで「プロモーション」が来る):私はこれらのユースケースのためにJavaプロジェクト(「アカウントを作成」、「パスワードを変更」など)を実装しました。オープンソースのGitHubは、Spring Securityの上にJavaで実装された、あなたの質問に完璧に答えます。

すべての説明があります(何かが足りない場合はお知らせください...)

ご覧ください: https://github.com/OhadR/oAuth2-sample/tree/master/authentication-flows

デモはこちら を参照してください。

Auth-flowを使用するクライアントWebアプリもあります。README=すべての説明付き: https://github.com/OhadR/Authentication-Flows =

3
OhadR

2つの一般的なソリューションがあります。

1. Creating a new password on the server and inform user from it.
2. Sending a unique URL to reset password.

最初のソリューションには多くの問題があり、使用するのは適切ではありません。これらはいくつかの理由です:

1. The new password which is created by server should be sent through an insecure channel (such as email, sms, ...) and resides in your inbox. 

2. If somebody know the email address or phone number of a user who has an account at a website then then it is possible to reset user password.

したがって、2番目のソリューションを使用することをお勧めします。ただし、次の問題を考慮する必要があります。

- The reset url should be random, not something guessable and unique to this specific instance of the reset process.

- It should not consist of any external information to the user For example, a reset URL should not simply be a path such as “.../?username=Michael”. 

- We need to ensure that the URL is loaded over HTTPS. No, posting to HTTPS is not enough, that URL with the token must implement transport layer 
  security so that the new password form cannot be MITM’d and the password the user creates is sent back over a secure connection.

- The other thing we want to do with a reset URL is setting token's expiration time so that the reset process must be completed within a certain duration.

- The reset process must run once completely. So, Reset URL can not be appilicable if the reset process is done completely once.

一般的な解決策は、URLパラメータとして送信できる一意のトークンを作成するURLを生成することです。これには、「Reset /?id = 2ae755640s15cd3si8c8i6s2cib9e14a1ae552b」などのURLが含まれます。

2
MMKarami

パスワードを忘れた場合の完全なコードを探している場合は、ここでコードを共有します。必要な場所にリンクを配置します。

<button> <a href="forgotpassword.jsp" style="text-decoration:none;">Forgot 
Password</a></button>

以下は私のforgotpassword.jspページです。

 <form id="register-form" role="form" class="form" method="post" 
 action="mymail_fp.jsp">
    <h3>Enter Your Email Below</h3>
   <input id="email" name="email" placeholder="Email address" class="form- 
   control"  type="email" required autofocus>
  <input name="recover-submit" class="btn btn-lg btn-primary btn-block" 
   value="Get Password" type="submit">
</form>

電子メールが送信されると、mymail_fp.jspページにリダイレクトされ、ユーザーに電子メールが送信されます。以下はmymail.jspページです。

<% 
mdjavahash md = new mdjavahash();
String smail =request.getParameter("email");
int profile_id = 0;
if(smail!=null)
{
 try{
// Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");

// Open a connection
Connection conn = 
DriverManager.getConnection("jdbc:mysql://localhost:3306/infoshare", "root", 
"");

Statement stmt = conn.createStatement();

 String sql1;
 sql1="SELECT  email FROM profile WHERE email = '"+smail+"'";

  ResultSet rs1=stmt.executeQuery(sql1);

if(rs1.first())
{
    String sql;
    sql = "SELECT Profile_id FROM profile where email='"+smail+"'";
     ResultSet rs2 = stmt.executeQuery(sql);

    // Extract data from result set
    while(rs2.next()){
       //Retrieve by column name
     profile_id  = rs2.getInt("Profile_id");
    }

    Java.sql.Timestamp  intime = new Java.sql.Timestamp(new 
    Java.util.Date().getTime());
    Calendar cal = Calendar.getInstance();
    cal.setTimeInMillis(intime.getTime());
    cal.add(Calendar.MINUTE, 20);
    Java.sql.Timestamp  exptime = new Timestamp(cal.getTime().getTime());

    int Rand_num = (int) (Math.random() * 1000000);
    String Rand = Integer.toString(Rand_num);
    String finale =(Rand+""+intime); // 
    String hash = md.getHashPass(finale); //hash code

    String save_hash = "insert into  reset_password (Profile_id, hash_code, 
   exptime, datetime) values("+profile_id+", '"+hash+"', '"+exptime+"', 
   '"+intime+"')";
    int saved = stmt.executeUpdate(save_hash);
    if(saved>0)
    {
  String link = "http://localhost:8080/Infoshare/reset_password.jsp";     
  //bhagawat till here, you have fetch email and verified with the email 
 from 
  datbase and retrived password from the db.
    //-----------------------------------------------
String Host="", user="", pass=""; 
Host = "smtp.gmail.com"; user = "[email protected]"; 
//"email@removed" // email id to send the emails 
pass = "xxxx"; //Your gmail password 
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; 
String to = smail;  
String from = "[email protected]";  
String subject = "Password Reset"; 
 String messageText = " Click <a href="+link+"?key="+hash+">Here</a> To 
  Reset 
  your Password. You must reset your password within 20 
  minutes.";//messageString; 
   String fileAttachment = ""; 
   boolean WasEmailSent ; 
  boolean sessionDebug = true; 
  Properties props = System.getProperties(); 
  props.put("mail.Host", Host); 
  props.put("mail.transport.protocol.", "smtp"); 
  props.put("mail.smtp.auth", "true"); 
  props.put("mail.smtp.", "true"); 
  props.put("mail.smtp.port", "465"); 
  props.put("mail.smtp.socketFactory.fallback", "false"); 
  props.put("mail.smtp.socketFactory.class", SSL_FACTORY); 
  Session mailSession = Session.getDefaultInstance(props, null); 
  mailSession.setDebug(sessionDebug); 
  Message msg = new MimeMessage(mailSession); 
  msg.setFrom(new InternetAddress(from)); 
  InternetAddress[] address = {new InternetAddress(to)}; 
  msg.setRecipients(Message.RecipientType.TO, address); 
  msg.setSubject(subject); 
  msg.setContent(messageText, "text/html");  
  Transport transport = mailSession.getTransport("smtp"); 
  transport.connect(Host, user, pass);
    %>
 <div class="alert success" style="padding: 30px; background-color: grey; 
  color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 
15% 20%;">
 <a href="forgotpassword.jsp"> <span class="closebtn" style="color: white; 
font-weight: bold; float: right; font-size: 40px; line-height: 35px; cursor: 
pointer; transition: 0.3s;">&times;</span> </a> 
 <h1 style="font-size:30px;">&nbsp;&nbsp; <strong>Check Your Email. Link To 
Reset Your Password Is Sent To : <%out.println(" "+smail); %></strong>  
</h1>
 <center><a href="forgotpassword.jsp"><h2><input type="button" value="OK"> 
</h2></a></center>
</div>
<%
try { 
transport.sendMessage(msg, msg.getAllRecipients()); 
WasEmailSent = true; // assume it was sent 
} 
catch (Exception err) { 
WasEmailSent = false; // assume it's a fail 
} 
 transport.close();
    //-----------------------------------------------
 }  
}   

 else{
    %>
    <div class="alert success" style="padding: 30px; background-color: grey; 
 color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 15% 20%;">
     <a href="forgotpassword.jsp"> <span class="closebtn" style="color: 
 white; font-weight: bold; float: right; font-size: 40px; line-height: 35px; 
 cursor: pointer; transition: 0.3s;">&times;</span> </a> 
     <h1 style="font-size:30px;">&nbsp;&nbsp; <strong>There Is No Email As 
 Such <%out.println(" "+smail); %></strong>Try Again  </h1>
     <center><a href="forgotpassword.jsp"><h2><input type="button" 
 value="OK"></h2></a></center>
    </div>
    <%      
 }  

stmt.close();
rs1.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}
}
 else{
    %>
 <div class="alert success" style="padding: 30px; background-color: grey; 
 color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 15% 20%;">
  <a href="forgotpassword.jsp"> <span class="closebtn" style="color: white; 
  font-weight: bold; float: right; font-size: 40px; line-height: 35px; 
 cursor: 
 pointer; transition: 0.3s;">&times;</span> </a> 
 <h1 style="font-size:30px;">&nbsp;&nbsp; <strong>Please Enter The Valid 
 Email Address</strong>  </h1>
 <center><a href="forgotpassword.jsp"><h2><input type="button" value="OK"> 
 </h2></a></center>
 </div>
  <%    
  }
  %> 

ここで私がやったことは、ユーザーにメールを送信する前に、送信時間を節約し、有効期限を0から1000000までの乱数を生成し、送信時間と連結して暗号化し、リンク内のクエリ文字列として送信しますEメール。そのため、メールが送信され、パスワードへのリンクがハッシュキーとともに送信されます。ユーザーがリンクをクリックすると、reset_password.jspに送信され、次はreset_password.jspページになります。

<%
String hash = (request.getParameter("key"));

Java.sql.Timestamp  curtime = new Java.sql.Timestamp(new 
Java.util.Date().getTime());

int profile_id = 0;
Java.sql.Timestamp exptime;

try{
// Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");

// Open a connection
Connection conn = 
DriverManager.getConnection("jdbc:mysql://localhost:3306/infoshare", "root", 
"");
Statement stmt = conn.createStatement();

 String sql = "select profile_id, exptime from reset_password where 
 hash_code ='"+hash+"'";
 ResultSet rs = stmt.executeQuery(sql);
 if(rs.first()){
 profile_id = rs.getInt("Profile_id");  
 exptime = rs.getTimestamp("exptime");

  //out.println(exptime+"/"+curtime);
  if((curtime).before(exptime)){        
      %>
      <div class="container">
       <form class="form-signin" action="update_reset.jsp" method="Post"> 
      <br/><br/>
         <h4 class="form-signin-heading">Reset Your Password Here</h4>
         <br> 
          <text style="font-size:13px;"><span class="req" 
        style="color:red">* </span>Enter New Password</text>
         <input type="password" id="inputPassword" name="newpassword" 
       class="form-control" placeholder="New Password" required autofocus>
         <br>
          <text style="font-size:13px;"><span class="req" 
         style="color:red">* </span>Enter New Password Again</text>
         <input type="password" id="inputPassword" name="confirmpassword" 
         class="form-control" placeholder="New Password Again" required>

          <input type="hidden" name="profile_id" value=<%=profile_id %>>
        <br>
         <button class="btn btn-lg btn-primary btn-block" 
    type="submit">Reset Password</button>
       </form>
     </div> <!-- /container -->
    <% } 
    else{
        %>
        <div class="alert success" style="padding: 30px; background-color: 
   grey; color: white; opacity: 1; transition: opacity 0.6s; width:50%; 
  margin: 10% 5% 15% 20%;">
             <a href="forgotpassword.jsp"> <span class="closebtn" 
   style="color: white; font-weight: bold; float: right; font-size: 40px; 
   line-height: 35px; cursor: pointer; transition: 0.3s;">&times;</span> 
   </a> 
             <h1 style="font-size:30px;">&nbsp;&nbsp; The Time To Reset 
  Password Has Expired.<br> &nbsp;&nbsp; Try Again </h1>
             <center><a href="forgotpassword.jsp"><h2><input type="button" 
     value="OK"></h2></a></center>
        </div>
       <%       
       }    
     }
   else{
    %>
    <div class="alert success" style="padding: 30px; background-color: grey; 
   color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 
    10% 5% 15% 20%;">
         <a href="forgotpassword.jsp"> <span class="closebtn" style="color: 
      white; font-weight: bold; float: right; font-size: 40px; line-height: 
       35px; cursor: pointer; transition: 0.3s;">&times;</span> </a> 
         <h1 style="font-size:30px;">&nbsp;&nbsp; The Hash Key DO Not Match. 
            <br/> &nbsp;&nbsp;&nbsp;Try Again!! </h1>
         <center><a href="forgotpassword.jsp"><h2><input type="button" 
         value="OK"></h2></a></center>
        </div>
    <%
    }
   // Clean-up environment
   rs.close();
   stmt.close();
   conn.close();
  }catch(SQLException se){
  //Handle errors for JDBC
  se.printStackTrace();
 }catch(Exception e){
  e.printStackTrace();
  }
%> 

このページでは、ハッシュキーを取得し、データベースハッシュキーと比較します。それが正しい場合は、有効期限を取得して現在の時間と比較します。パスワードをリセットする時間が経過していない場合、パスワードをリセットするフォームを表示します。そうでない場合、エラーメッセージがスローされます。時間が経過していない場合、フォームを表示し、フォームが送信されると、update_reset.jspにリダイレクトされ、次にupdate_reset.jspページが表示されます。

 <%  
 mdjavahash md = new mdjavahash();
 String profile_id= request.getParameter("profile_id");
 String np= request.getParameter("newpassword");
 String cp = request.getParameter("confirmpassword");
 //out.println(np +"/"+ cp);

 if( np.equals(" ") || cp.equals(" ")){%>
 <div class="alert success" style="padding: 30px; background-color: grey; 
 color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 15% 20%;">
     <a href="reset_password?profile_id=<%=profile_id%>"> <span 
  class="closebtn" style="color: white; font-weight: bold; float: right; 
    font-size: 40px; line-height: 35px; cursor: pointer; transition: 
   0.3s;">&times;</span> </a> 
     <h1 style="font-size:30px;">&nbsp;&nbsp; Please Fill Both The Fields 
    </h1>
     <center><a href="reset_password?profile_id=<%=profile_id%>""><h2><input 
    type="button" value="OK"></h2></a></center>
   </div>   
   <% }
   else if(!np.equals(cp)){
    %>
    <div class="alert success" style="padding: 30px; background-color: grey; 
  color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
  5% 15% 20%;">
         <a href="reset_password?profile_id=<%=profile_id%>"> <span 
     class="closebtn" style="color: white; font-weight: bold; float: right; 
        font-size: 40px; line-height: 35px; cursor: pointer; transition: 
             0.3s;">&times;</span> </a> 
         <h1 style="font-size:30px;">&nbsp;&nbsp; The Two Passwords Do Not 
        Match. Try Again </h1>
         <center><a href="reset_password?profile_id=<%=profile_id%>"><h2> 
           <input type="button" value="OK"></h2></a></center>
        </div>
      <%        
     }
    else{   
      try{
        // Register JDBC driver
        Class.forName("com.mysql.jdbc.Driver");

        // Open a connection
        Connection conn = 
        DriverManager.getConnection("jdbc:mysql://localhost:3306/infoshare", 
      "root", "");
        // Execute SQL query
        Statement stmt = conn.createStatement();
        stmt.executeUpdate("update profile set 
       password='"+md.getHashPass(np)+"' where Profile_id="+profile_id+"");
        //response.sendRedirect("mainpage.jsp");
        %>
        <div class="alert success" style="padding: 30px; background-color: 
       grey; color: white; opacity: 1; transition: opacity 0.6s; width:65%; 
      margin: 10% 5% 15% 20%;">
         <a href="login.jsp"> <span class="closebtn" style="color: white; 
        font-weight: bold; float: right; font-size: 40px; line-height: 35px; 
         cursor: pointer; transition: 0.3s;">&times;</span> </a> 
         <h1 style="font-size:30px;">&nbsp;&nbsp; The Password Is 
            Successfully Reset.<br>&nbsp;&nbsp; Try Login With New 
             Password</h1>
         <br><br><center><a href="login.jsp"><p style="font-size:20px"> 
            <input type="button" style="width:40px; height:35px;" 
        value="OK"></p></a> 
        </center>
           </div>                   
          <%
           stmt.close();
           conn.close();
        }catch(SQLException se){
          //Handle errors for JDBC
           se.printStackTrace();
        }catch(Exception e){
        //Handle errors for Class.forName
         e.printStackTrace();
       }    
  } 
%>

このページでは、最初にフィールドを検証してから、新しいパスワードでデータベースを更新します。それは長いですが、動作しますが。私はここでMD5暗号化技術を使用しましたが、どうすればいいですか?リンクをたどってください JavaScriptでJSPのログインパスワードを保護するためにMD5ハッシュを使用する方法?

1
Bhagawat