在 Java 中实现忘记密码功能
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27313352/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Implementing forgot password functionality in Java
提问by vigamage
I am currently implementing a forgot password function in a Java project. my methodology is,
我目前正在 Java 项目中实现忘记密码功能。我的方法是,
- User clicks the forgot password link.
- In the forgot password page, system prompts the user to enter the email address he/she has registered in to the system.
- An email which contains a link to reset the password is sent to the given email address in step above.
- User clicks the link and he/she get redirected to a page(reset password) where user can enter his new password.
- In Reset Password page, the field "email address" is filled automatically and it cannot be changed.
- Then user enter his new password and the field related to the email address in the database is updated.
- 用户单击忘记密码链接。
- 在忘记密码页面,系统提示用户输入他/她在系统中注册的电子邮件地址。
- 包含重置密码链接的电子邮件将发送到上述步骤中的给定电子邮件地址。
- 用户点击链接,他/她被重定向到一个页面(重置密码),用户可以在其中输入他的新密码。
- 在重置密码页面中,“电子邮件地址”字段是自动填写的,无法更改。
- 然后用户输入他的新密码,数据库中与电子邮件地址相关的字段就会更新。
Although I have restricted the email address
field in the reset password page from editing (a read only field) any one can alter the url in the address bar of the browser and change the email address field.
尽管我限制了email address
重置密码页面中的字段(只读字段),但任何人都可以更改浏览器地址栏中的 url 并更改电子邮件地址字段。
How Do I restrict every user from altering the email address in the reset password page?
如何限制每个用户更改重置密码页面中的电子邮件地址?
采纳答案by clement
You have to save it in DB before sending email by using token:
您必须在使用令牌发送电子邮件之前将其保存在数据库中:
- When user click on "send me a email with reset instructions", you create one record in DB with those fields:
email
,token
,expirationdate
- User receive email with yourwwebsite.com/token and click on it
- With the
token
in the Url, server canidentify the user
, check if request is not expired thanks to expirationdate, put right email into the box, and ask for password renewal. User type new passwords and you have to give the token (hidden field
in the form) + passwords to the server. Server don't care about the textbox for the email becausewith the token, user is identified strongly
- Then server check if token still valid with
expirationdate
(again), check ifpassword match
and if all is ok, save new password! Server can send again message in order to inform user that password has been changed due to the request.
- 当用户点击“给我发送一封带有重置说明的电子邮件”时,您会在数据库中创建一个包含以下字段的记录:
email
,token
,expirationdate
- 用户收到带有 yourwwebsite.com/token 的电子邮件并点击它
- 使用
token
Url 中的 ,服务器可以identify the user
检查请求是否由于过期日期而过期,将正确的电子邮件放入框中,并要求更新密码。用户输入新密码,您必须将令牌(hidden field
以表格形式)+ 密码提供给服务器。服务器不关心电子邮件的文本框,因为with the token, user is identified strongly
- 然后服务器检查令牌是否仍然有效
expirationdate
(再次),检查password match
是否一切正常,保存新密码!服务器可以再次发送消息以通知用户密码已因请求而更改。
This is really safe. Please use short time for the expirationdate
to improove the security (for instance 5 minutes is correct for me) and use strong token (as GUID, see comments)
这真的很安全。请使用较短的时间expirationdate
来提高安全性(例如 5 分钟对我来说是正确的)并使用强令牌(作为 GUID,请参阅评论)
回答by Yagnesh Agola
You cant restrict email address to being change by user.
Email address can easily change by editing source code in browser even though you have taken hidden or make text box as readonly.
您不能将电子邮件地址限制为由用户更改。
即使您将文本框隐藏或设为只读,也可以通过在浏览器中编辑源代码轻松更改电子邮件地址。
You may provide uniq random string or token
with reset link and
check the email address and token combination after clicking on reset password link or after user submitted request for the reset password by checking email address and token string in request with the email address and token string in your database.
您可以uniq random string or token
在点击重置密码链接后或在用户提交重置密码请求后提供重置链接并检查电子邮件地址和令牌组合,方法是检查请求中的电子邮件地址和令牌字符串以及数据库中的电子邮件地址和令牌字符串。
If email address is present in your database that means email address is valid, if not than give message that email address doesn't exist in you user records.
如果您的数据库中存在电子邮件地址,则意味着电子邮件地址是有效的,如果没有,则给出电子邮件地址不存在于您的用户记录中的消息。
NOTE :
If your are using any framework or simply servlet than it better to provide link, so that you can validate email and token string before you display your reset password form. If token string or email address in invalid than you can restrict user from submit request for reset password and validate after submitting request. It will more secure than validating after submitting reset password request.
注意:
如果您使用任何框架或简单的 servlet,那么最好提供链接,以便您可以在显示重置密码表单之前验证电子邮件和令牌字符串。如果令牌字符串或电子邮件地址无效,您可以限制用户提交重置密码请求并在提交请求后进行验证。它比提交重置密码请求后验证更安全。
回答by ecrisostomo
I agree with the answer given by @clement if you HAVE to implement the forgot password functionality yourself. Sounds like a reasonable and secure way for this implementation.
如果您必须自己实现忘记密码功能,我同意 @clement 给出的答案。对于这种实现来说,这听起来像是一种合理且安全的方式。
However, as an alternative, if you don't have to implement it yourself I would suggest to use a service that does this for you, like Stormpath.
但是,作为替代方案,如果您不必自己实现它,我建议您使用为您执行此操作的服务,例如Stormpath。
If you decide to use Stormpath, the code that would trigger the functionality would look like this in Java (with Stormpath's Java SDK):
如果您决定使用 Stormpath,触发该功能的代码在 Java 中将如下所示(使用 Stormpath 的 Java SDK):
Account account = application.sendPasswordResetEmail("[email protected]");
Your user would get an email with a link like:
您的用户会收到一封包含以下链接的电子邮件:
http://yoursite.com/path/to/reset/page?sptoken=$TOKEN
And then, when the user clicks on the link, you would verify and reset the password like this:
然后,当用户单击链接时,您将像这样验证和重置密码:
Account account = application.resetPassword("$TOKEN", "newPassword");
The details about how this works can be found in the Stormpath's password reset documentation.
有关其工作原理的详细信息可以在 Stormpath 的密码重置文档 中找到。
With this approach you don't have to implement and maintain the functionality for yourself if you have the option not to do so.
使用这种方法,如果您可以选择不这样做,您就不必为自己实现和维护功能。
Note: Stormpath has joined Okta.
注意: Stormpath 已加入Okta。
回答by MMKarami
There are two common solutions:
有两种常见的解决方案:
1. Creating a new password on the server and inform user from it.
2. Sending a unique URL to reset password.
The first solution has a lot of problems and is not appropriate to use. These are some reasons:
第一种方案有很多问题,不适合使用。这些是一些原因:
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.
So, second solution is better to use. However, you should consider following issues:
所以,第二种解决方案更好用。但是,您应该考虑以下问题:
- 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.
The common solution can be generating a URL to create a unique token which can be sent as URL parameter, it contains a URL such as “Reset/?id=2ae755640s15cd3si8c8i6s2cib9e14a1ae552b”.
常见的解决方案是生成一个 URL 来创建一个可以作为 URL 参数发送的唯一令牌,它包含一个 URL,例如“Reset/?id=2ae755640s15cd3si8c8i6s2cib9e14a1ae552b”。
回答by OhadR
This question has been posted 3 years before this answer... Yet I think it might be helpful to others.
这个问题已经在这个答案之前 3 年发布了......但我认为它可能对其他人有帮助。
So in short: I totally agree with your flow. Looks very secured, and your only open end makes also sense - how can you make sure that no one changes the username, and by that can set a new password for him.
简而言之:我完全同意你的流程。看起来很安全,你唯一的开放端也很有意义——你怎么能确保没有人改变用户名,从而可以为他设置一个新密码。
I like less the idea of storing temporarily things is the DB (as the accepted answer suggests).
我不太喜欢临时存储东西的想法是数据库(正如公认的答案所暗示的那样)。
The idea I was thinking about was to sign the data in the link that is sent to the user. Then, when the user clicks the link and the server receives the call, the server also gets the encrypted part and can validate that the data was untouched.
我正在考虑的想法是对发送给用户的链接中的数据进行签名。然后,当用户单击链接并且服务器收到调用时,服务器也会获取加密部分并可以验证数据是否未受影响。
By the way (here comes a "promotion"): I have implemented a JAVA project for these use cases (also "create account", "change password" etc.). It is free on GitHub, open source. It answers your question perfectly... implemented in Java, on top of Spring Security.
顺便说一句(这里有一个“促销”):我已经为这些用例实现了一个 JAVA 项目(还有“创建帐户”、“更改密码”等)。它在 GitHub 上免费,开源。它完美地回答了你的问题......在 Spring Security 之上用 Java 实现。
There are explanation for everything (and if something is missing - let me know...)
一切都有解释(如果有什么遗漏 - 让我知道......)
Have a look: https://github.com/OhadR/oAuth2-sample/tree/master/authentication-flows
看看:https: //github.com/OhadR/oAuth2-sample/tree/master/authentication-flows
See a Demo here.
There is also a client web-app that uses the auth-flows, with the README with all explanations: https://github.com/OhadR/Authentication-Flows
还有一个使用 auth-flows 的客户端 web 应用程序,带有所有解释的自述文件:https: //github.com/OhadR/Authentication-Flows
回答by Bhagawat
If You are looking for the complete code to implement forgot password, here I share my code. Put the link where you need.
如果您正在寻找实现忘记密码的完整代码,我在这里分享我的代码。把链接放在你需要的地方。
<button> <a href="forgotpassword.jsp" style="text-decoration:none;">Forgot
Password</a></button>
Below is my forgotpassword.jsp
page.
下面是我的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>
Once the email is submitted, then it is redirected to mymail_fp.jsp
page where i send the email to the user.
Below is mymail.jsp
page.
提交电子邮件后,它会被重定向到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;">×</span> </a>
<h1 style="font-size:30px;"> <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;">×</span> </a>
<h1 style="font-size:30px;"> <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;">×</span> </a>
<h1 style="font-size:30px;"> <strong>Please Enter The Valid
Email Address</strong> </h1>
<center><a href="forgotpassword.jsp"><h2><input type="button" value="OK">
</h2></a></center>
</div>
<%
}
%>
Now what i have done here is, before sending email sending email to the user, I save sent time, expire time, generate random number from 0 to 1000000 and concatenate with sent time and encrypt is and send it as query string in the link in the email. So email will be sent and link to password will be sent along with the hash key. Now when user clicks on the link, they are sent to reset_password.jsp and following is reset_password.jsp
page.
现在我在这里所做的是,在向用户发送电子邮件之前,我保存发送时间,过期时间,生成从 0 到 1000000 的随机数,并与发送时间连接并加密,并将其作为查询字符串发送到链接中电子邮件。所以电子邮件将被发送,密码链接将与哈希键一起发送。现在,当用户点击链接时,它们会被发送到 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;">×</span>
</a>
<h1 style="font-size:30px;"> The Time To Reset
Password Has Expired.<br> 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;">×</span> </a>
<h1 style="font-size:30px;"> The Hash Key DO Not Match.
<br/> 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();
}
%>
In this page i fetch hash key and compare with database hash key and it it is true, then i fetch expire time and compare with current time. If time to reset password has not expired then i show form to reset password otherwise i throw error message. If time has not expired then i show form and when the form is submitted, it is redirected to update_reset.jsp
and following is my update_reset.jsp
page.
在此页面中,我获取散列键并与数据库散列键进行比较,这是真的,然后我获取过期时间并与当前时间进行比较。如果重置密码的时间尚未到期,那么我会显示表格以重置密码,否则我会抛出错误消息。如果时间没有过期,那么我会显示表单,当表单被提交时,它被重定向到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;">×</span> </a>
<h1 style="font-size:30px;"> 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;">×</span> </a>
<h1 style="font-size:30px;"> 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;">×</span> </a>
<h1 style="font-size:30px;"> The Password Is
Successfully Reset.<br> 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();
}
}
%>
In this page i validate the fields first and then i update the database with the new password. Though it is long but it works. I have used MD5 encryption technique here and if you want how to do then follow the link How to Use MD5 Hash for securing Login passwords in JSP with Javascript?
在此页面中,我首先验证字段,然后使用新密码更新数据库。虽然它很长,但它有效。我在这里使用了 MD5 加密技术,如果您想怎么做,请按照链接如何使用 MD5 Hash 使用 Javascript 在 JSP 中保护登录密码?