目录
1、实现分析
2、步骤
1)新建login.jsp
2)修改LoginServlet:
3)启动访问:
3、安全性考虑
4、最佳实践思路
1)选择安全的认证机制
2)强化会话管理
3)安全地存储用户凭证
4)使用HTTPS
5)安全的Cookie管理
6)定期审查和更新
7)提醒用户
1、实现分析
需求:勾选“记住我” ,下次访问登陆页面自动填充用户名密码。
怎样自动填充用户名和密码?
记住我功能要实现的效果:用户把浏览器关闭过几天再来访问也能自动填充,所以需要将登陆信息存入一个可以长久保存、并且能够在浏览器关闭、重启后依然有效的地方:使用Cookie。
将用户名和密码写入Cookie中,并持久化存储Cookie,下次访问浏览器,发送请求时会自动携带Cookie。
在页面获取Cookie数据后,设置到用户名和密码框中。
什么时候写入Cookie?
登录成功且用户在登录页面勾选了记住我的复选框。
流程分析:
- 前端需要在发送请求和数据的时候,多携带一个用户是否勾选记住我的数据。
- LoginServlet获取到数据后,调用Service完成用户名和密码的验证。
- 登录成功,并且用户在前端勾选了记住我,需要向Cookie中写入用户名和密码的数据,并设置Cookie的有效期。
- 将数据响应给前端,这时后端返回给前端的Cookie数据就已经存储好了。
- 在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中。
2、步骤
1)新建login.jsp
(在webapp中新建;使用JSP相较于HTML在此功能更有优势)
JSP(Java Server Pages):Java 服务端页面。是一种动态的网页技术,其中既可以定义 HTML、JS、CSS等静态内容,也可以定义 Java代码的动态内容,也就是 JSP = HTML + Java。
作用:简化开发,避免了在Servlet中直接输出HTML标签(频繁地用
response.getWriter().write()
方法输出)。JSP页面最终会被Web服务器(Tomcat)转换为Servlet,然后由Java Web容器执行。
使用Servlet进行逻辑代码开发、使用JSP进行数据展示,已过时。
更多的是使用 HTML + Ajax:
使用Servlet进行后端逻辑代码开发、使用HTML进行数据展示:HTML是静态页面,怎么进行动态数据展示呢?这就是 Ajax 的作用了。
为了可以在页面获取Cookie中的数据,使用JSP+EL表达式:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="login" method="post" id="form">
<p>用户名:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
<p>密码:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>
<p>记住我<input id="remember" name="remember" value="1" type="checkbox"></p>
<input type="submit" class="button" value="登录">
</form>
</div>
</body>
</html>
EL表达式:
主要作用是获取数据。其实就是从域对象中获取数据,然后将数据展示在页面上。
${cookie.key.value}
key:存储在Cookie中的键的名称
获取用户名的值: ${cookie.username.value}
获取密码的值:${cookie.password.value}
JavaWeb中有四大域对象:
page:当前页面有效
request:当前请求有效,包括请求转发forward
session:当前会话有效
application:当前应用有效EL表达式获取数据会依次从这4个域(作用范围从小到大)中寻找,直到找到为止。
记住我复选框:在用户提交表单时,前端将checkbox的状态(选中或未选中,这里将选中的值设为1,用于之后的判断)作为请求的一部分发送到后端。
2)修改LoginServlet:
获取复选框的值并在登录成功后进行设置Cookie
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取参数
String username = request.getParameter("username");
String password = request.getParameter("password");
//获取复选框数据
String remember=request.getParameter("remember");
//使用MyBatis查询数据表,如果用传来的参数能查到User对象,则成功登录
//MyBatis的使用步骤
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUser(username, password);
sqlSession.close();
//进行响应
response.setContentType("text/html;charset=utf-8");
PrintWriter writer=response.getWriter();
if (user!=null){
//登录成功
//判断用户是否勾选记住我,字符串写前面是为了避免出现空指针异常
if("1".equals(remember)){
//勾选了记住我 :发送Cookie
//1. 创建Cookie对象
Cookie c_username=new Cookie("username",username);
Cookie c_password=new Cookie("password",password);
// 设置Cookie的有效期
c_username.setMaxAge(60*60*24*7); //一周
c_password.setMaxAge(60*60*24*7);
//2. 发送
response.addCookie(c_username);
response.addCookie(c_password);
}
//将登陆成功后的user对象存储到session
HttpSession session=request.getSession();
session.setAttribute("user",user);
request.setAttribute("username",username);
request.getRequestDispatcher("/SuccessServlet").forward(request,response);
}else {
writer.write("登录失败");
}
}
}
3)启动访问:
只有用户名和密码输入正确且勾选了remeber的复选框,在响应头中才可以看到Cookie数据:
登录成功并勾选了记住我后,后端返回给前端的Cookie数据就已经存储好了,接下来就需要在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中。
关闭浏览器,重新访问登录页面:用户和密码已经被填充。
3、安全性考虑
虽然确实可以将用户名和密码(或任何其他数据)写入Cookie中,并通过设置Cookie的持久化属性使其在浏览器中保存一段时间,但是不推荐将敏感信息如用户名和密码存储在Cookie中。
原因:
-
安全性问题:Cookie是在客户端(即用户的浏览器)上存储的。这意味着任何能够访问用户计算机的人或恶意软件都可以读取这些Cookie。因此,将用户名和密码存储在Cookie中会使这些信息面临被窃取的风险。
-
明文存储:默认情况下,Cookie是以明文形式存储的。即使通过某种方式加密了用户名和密码,但如果加密密钥也存储在客户端或易于访问的地方,那么这些信息仍然可能面临被解密的风险。
-
会话劫持:如果攻击者能够获取到包含用户名和密码的Cookie,他们可以伪造请求,冒充合法用户访问应用,导致会话劫持。
更好的做法:
使用安全的认证机制,如OAuth、OpenID Connect或简单的用户名/密码认证结合安全的会话管理。在用户名/密码认证中,当用户成功登录后,服务器应该生成一个唯一的会话ID,并将其存储在Cookie中。这个会话ID应该与服务器上的会话数据相关联,而不是直接包含用户名和密码。其中可以包含用户的认证状态和其他必要信息。这样,即使敏感信息不在客户端存储,也可以通过会话ID识别已登录的用户。即使Cookie被窃取,攻击者也无法直接获取到用户的敏感信息。同时,使用HTTPS来加密浏览器和服务器之间的通信也是至关重要的,即使Cookie被窃取,攻击者也无法轻易解密其中的内容,从而确保Cookie在传输过程中的安全性。
总之,虽然技术上可以将用户名和密码写入Cookie并持久化存储,但出于安全考虑,应该采用更安全的会话管理方法来处理用户的认证状态。
4、最佳实践思路
安全地使用“记住我”功能的最佳实践涉及到多个方面,包括认证机制的选择、会话管理的安全性以及用户凭证的存储和处理。以下是一些关键的最佳实践:
1)选择安全的认证机制
-
OAuth/OpenID Connect:使用OAuth或OpenID Connect等第三方认证服务。这些服务提供了更高级别的安全性,并且通常已经解决了许多常见的安全问题。
-
用户名/密码认证:如果使用传统的用户名/密码认证,确保密码策略足够强大,包括要求密码长度、复杂度,并定期提示用户更改密码。
2)强化会话管理
-
会话ID:使用不可预测的、足够长的会话ID,并且不要将用户ID或其他敏感信息直接编码在会话ID中。
-
会话有效期:限制“记住我”功能的会话有效期,避免长期会话带来的潜在风险。定期要求用户重新登录,特别是在敏感操作之前。
-
会话固定:实施会话固定策略,以防止会话劫持。当用户登录时,生成一个新的会话ID,并立即使之前的会话ID失效。
-
会话注销:提供明确的会话注销机制,并确保在用户登出时,所有相关的会话数据都被正确清除。
3)安全地存储用户凭证
-
加密存储:用户凭证(如密码)应始终以加密形式存储,并且使用强加密算法和安全的密钥管理策略。
-
避免明文存储:绝不要以明文形式存储或传输用户凭证,包括在“记住我”功能的实现中。
4)使用HTTPS
- 全程加密:确保整个应用都使用HTTPS,包括登录页面、认证过程和所有后续的用户交互。这可以防止中间人攻击和数据泄露。
5)安全的Cookie管理
-
HttpOnly和Secure标志:为存储会话信息的Cookie设置HttpOnly和Secure标志,防止跨站脚本攻击(XSS)和未加密的传输。
-
避免在Cookie中存储敏感信息:不要在Cookie中直接存储用户的敏感信息,如密码或加密密钥。
6)定期审查和更新
-
安全检查:定期检查是否有潜在的安全漏洞或风险。
-
更新和补丁:及时应用安全更新和补丁,以修复已知的安全问题。
7)提醒用户
-
不要在公共计算机或不受信任的网络环境中使用此功能。
-
定期查看和修改他们的登录信息。