浅析博客园的保存密码并自动登录, 然后自己写一个demo
现在的网站基本上都有保存密码并自动登录的功能, 那么密码到底保存在哪里呢? 你会发现, 同一个网站, 如果换一台电脑或者换一个浏览器那就需要重新输入用户名和密码, 从这里可以看出, 密码是保存在浏览器的. 今天就来分析一下博客园的登录并自己写一个demo. 密码是保存在浏览器的cookie中的, 那什么叫cookie呢? w3cshool中有这样的定义: cookie 是存储于访问者的计算机中的变量。每当同一台计算机通过浏览器请求某个页面时,就会发送这个 cookie。是的, 也就是说, 只要浏览器访问服务器并且cookie是存在的(当然, 路径有效才行 ), 浏览器就会携带cookie到服务器. 这是一种自发的行为, 并不需要设置. 就好像你每天出门一定会带手机一样, 并不需要别人提醒你.
好了, 进入正题. 看一下博客园是怎么保存密码的. 假如我没有登录过博客园, 打开到博客园的登录界面, 然后再浏览器中查看一下关于博客园的cookie, 可以看的这样的:

有3个cookie,分别是这样的3个:
并没有关于用户名密码的cookie, 嘿嘿, 因为我此时根本没有登录. 好了, 现在登录

开始我并没有勾选下次自动登录的选项, 登录之后重定向到首页. 在开发者工具的network中看一下

可以看到, 多了一个.CNBlogsCookie的请求cookie, 咦咦咦, 刚才没有的啊, 现在就有了, 于是我猜测这就是用户名和密码的cookie, 这只是暂时的一个猜测. 在内容设置中看一下关于这个cookie更详细的信息

刚才总共3个, 现在总共5个, 比刚才多了2个, 看一下多的2个

另外一个是这样的:

可以看到, 2个cookie都是关闭浏览器时过期, 现在关闭浏览器? 不不不, 先看一下哪个保存着登录信息, 先把SERVERID这个cookie删除, 看一下是否还保持着登录状态. 试一下你会发现此时仍保持登录状态, 但是如果把.CNBlogsCookie这个cookie删掉, 你会发现'掉线'了. 由此可以说明登录信息保存在.CNBlogsCookie这个cookie中, 啊嘞, 我为什么要说登录信息呢? 刚才不是说的用户名密码么? 登录信息不就是指的用户名和密码吗? 我觉得不一定要把密码存到cookie中, 可以把一串和密码相关的字符串存到cookie中, 在进行cookie自动登录时, 在数据库中查询用户名和这个字符串, 在表单提交时, 查询用户名和密码, 个人觉得这样更安全. 如果是把用户名和密码保存在了cookie中, 那么就算销毁session退出登录, 下次登录时, 在登录页面浏览器应该自动填写用户名和密码才对(毕竟cookie里面有啊). 就博客园来说, 并不是这样的, 可能你会反驳: "我明明退出登录下次再登录时用户名和密码就在那儿啊.", 其实这并不是cookie的功能, 而是浏览器自己帮你做的, 现在的浏览器一般都有个
的功能, 好吧, 这个我想这也算个cookie, 不过此cookie非彼cookie, 呃, 有点混乱, 我也不确定这个功能算不算cookie. 总之, 关闭这个功能, 然会退出登录再打开博客园你会发现登录信息是没有的, 所以通过分析我觉得cookie中不一定保存了密码.
那么这个cookie是服务器什么时候传给浏览器的, 上面看到的是request cookie, 那么服务器是什么时候response的, 登录是ajax请求登录的, 你可以用浏览器打断点看一下, 可以发现.CNBlogsCookie是登录请求时response回来的, 如下图

好了, 现在也知道登录信息保存在哪里了, 现在关闭浏览器再打开, 你会发现此时登录状态已经没有了, 因为保存登录信息的cookie已经消失了, 关闭浏览器即代表结束一次会话(销毁session). 嗯, 到这里我们基本知道博客园是怎么保存密码的(可能并没有把密码保存在cookie中, 可能只是保存了一个与密码相关的字符串, 也可能保存的是经过加密的密码), 只是没有将cookie保存在客户端硬盘中而已(没有勾选下次自动登录), 那么是怎么实现自动登录呢? 想想cookie的工作原理: 浏览器每次请求服务器, 如果存在cookie, 并且域和路径符合要求, 都会将cookie携带至服务器.(域和路径看上面的图) 所以只要访问该网站, 服务器判断cookie中的值, 然后判断用户是否可以改为登录状态就行了.
所以登录页面勾选下次自动登录就是表示将cookie存到硬盘中, 大概清楚了博客园保存密码并自动登录的方式: 首次登录时将登录信息保存到cookie中, 下次登录时浏览器携带包含登录信息的cookie到服务器校验从而显示自动登录
自己写一个保存密码并自动登录的demo, 两个页面loginUI.jsp和successUI.jsp, loginUI.jsp用于登录, successUI.jsp只有成功登录之后才能访问
loginUI.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>cookie login</title> <script type="text/javascript" src="js/jquery-1.8.3.min.js"></script> <script type="text/javascript"> function checkCookie(){ //把用户名取出来 var username = getCookie("loginInfo"); $(":text").val(username); } function getCookie(c_name){ if(document.cookie.length > 0){ var coo = document.cookie; var start = coo.indexOf(c_name+"=")+c_name.length+1; var end = coo.indexOf(";", start); if(end == -1) end = coo.length; var cookieValue = decodeURIComponent(coo.substring(start, end)); return cookieValue.split(",")[0]; } } </script> </head> <body onload="checkCookie()"> <form action="cookie_login.action" method="post"> 用户名<input type="text" name="user.username"><br> 密码<input type="password" name="user.password"><br> <input type="submit" value="登录"> </form> </body> </html>
这里也涉及到了用js操作cookie
successUI.jsp
<body> 登陆后才能看见我 </body>
后台是struts2, 为了方便, 没有写service, 直接写的dao查询数据库
struts.xml
<package name="cookie-action" namespace="/" extends="struts-default"> <action name="cookie_*" class="top.bwcx.cookie.action.LoginAction" method="{1}"> <result name="loginUI">/WEB-INF/jsp/cookie/loginUI.jsp</result> <result name="successUI">/WEB-INF/jsp/cookie/successUI.jsp</result> <result name="success" type="redirectAction"> <param name="actionName">cookie_successUI</param> </result> </action> </package>
LoginAction.java
import java.net.URLEncoder; import java.util.UUID; import javax.servlet.http.Cookie; import org.apache.struts2.ServletActionContext; import top.bwcx.cookie.dao.UserDao; import top.bwcx.cookie.entity.User; import com.opensymphony.xwork2.ActionSupport; @SuppressWarnings("serial") public class LoginAction extends ActionSupport { private User user; private UserDao userDao = new UserDao(); public String loginUI(){ return "loginUI"; } public String successUI(){ return "successUI"; } //用于表单登录 public String login(){ try { if(user != null){//表单登录 //生成一串字符串与密码关联保存到cookie中, 并存到数据库 user = userDao.findByUsernameAndPassword(user); if(user != null){ //登录成功, 将登录信息保存在session中 ServletActionContext.getRequest().getSession().setAttribute("loginUser", user); //生成一串uuid并保存到hobby中, 这里应该是注册就生成的, 可是这里没有注册, 就第一次登录的时候生成 //这个条件不用管, 根据你的实际情况判断就行了 if(user.getHobby() == null || user.getHobby().equals("") || user.getHobby().contains("^")){ String hob = UUID.randomUUID().toString().replace("-", ""); user.setHobby(hob); //更新 userDao.updateUser(user); } //将用户名和uuid添加到cookie String s = user.getUsername()+","+user.getHobby(); //编码, 因为cookie不能包含逗号、分号或空格,也不能以 $ 字符开头 String loginInfo = URLEncoder.encode(s, "UTF-8"); Cookie loginCookie = new Cookie("loginInfo", loginInfo); loginCookie.setMaxAge(60*60); loginCookie.setPath("/"); ServletActionContext.getResponse().addCookie(loginCookie); } }else return "loginUI"; } catch (Exception e) { e.printStackTrace(); } return "success"; } public User getUser() { return user; } public void setUser(User user) { this.user = user; }
我的表示本来就存在的表, 里面有id, 用户名, 密码和爱好几个字段, 我把爱好改成了由UUID生成的一串字符串, cookie自动登录的时候就判断这个字符串和用户名. 我做了一个登陆的过滤器, cookie的自动登录就放在里面了
setMaxAge(int expiry)表示cookie的过期时间, 负数表示关闭浏览器cookie过期, 0表示立刻删除cookie, 正数表示在该时间后过期, 单位秒.
LoginFilter.java
/** * 服务器会为每个访问它的的浏览器创建一个session, 那么服务器怎么识别每个浏览器呢? 这时就需要用到JSESSIONID, 所以说JSESSIONID的作用是: * JSESSIONID作为服务器识别每个浏览器的唯一标识 * 在浏览器端将JSESSIONID删除, 服务器端的session会失效 */ public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String uri = req.getRequestURI(); // System.out.println(uri); if(uri.contains("cookie_login")) chain.doFilter(req, resp); else{ //查看session //登录后在浏览器端将JSESSIONID删除, 看一下这里的session Object user = req.getSession().getAttribute("loginUser"); if(user == null){ //如果session为空, 就尝试cookie登录 boolean isLogin = CookieUtil.loginByCookies(req, resp); if(isLogin) chain.doFilter(request, response); //cookie登录成功 else resp.sendRedirect(req.getContextPath()+"/cookie_loginUI.action"); } else chain.doFilter(req, resp); } } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } }
用于cookie登录的代码
CookieUtil.java
public class CookieUtil { private static UserDao userDao = new UserDao(); public static boolean loginByCookies(HttpServletRequest request, HttpServletResponse response) { try { Cookie[] cookies = request.getCookies(); if(cookies != null && cookies.length > 0){ for (Cookie cookie : cookies) { String cookieName = cookie.getName(); if(cookieName.equals("loginInfo")){ String cookieValue = URLDecoder.decode(cookie.getValue(), "UTF-8"); // System.out.println(cookieValue); User user = userDao.findByUsernameAndHobby(cookieValue.split(",")[0], cookieValue.split(",")[1]); if(user != null){ //登录成功, 将登录信息保存在session中 request.getSession().setAttribute("loginUser", user); return true; } } } } } catch (Exception e) { e.printStackTrace(); } return false; } }
以上是这个案例的主要代码, 下面把一些其他用到的代码也贴到下面
UserDao.java
public class UserDao { public User findByUsernameAndPassword(User user) { try { String sql = "select * from user where username = ? and password = ?"; return DbTools.getQueryRunner().query(sql, new BeanHandler<User>(User.class), user.getUsername(), user.getPassword()); } catch (Exception e) { e.printStackTrace(); } return null; } public void updateUser(User user) { try { String sql = "update user set hobby = ? where id = ?"; DbTools.getQueryRunner().update(sql, user.getHobby(), user.getId()); } catch (SQLException e) { e.printStackTrace(); } } public User findByUsernameAndHobby(String username, String hobby) { try { String sql = "select * from user where username = ? and hobby = ?"; return DbTools.getQueryRunner().query(sql, new BeanHandler<User>(User.class), username, hobby); } catch (Exception e) { e.printStackTrace(); } return null; } }
操作数据库用的是c3p0和dbutils, 这方面的内容可以在我以前的随笔中找到, 里面获取QueryRunner对象时这样得到的
public class DbTools { private static QueryRunner qr; static{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); qr = new QueryRunner(dataSource); } public static QueryRunner getQueryRunner(){ return qr; } }
User.java
public class User { private Integer id; private String username; private String password; private String hobby; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } }
当然不要忘了web.xml的配置
<!-- 登录过滤器放在struts过滤器的前面 -->
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>top.bwcx.cookie.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
第一次登录时在登录页面进行登录http://localhost:8080/blogTest/cookie_loginUI.action
下次再打开登录页面时,页面会自动从cookie中取出用户名并填写在文本框中。怎么证明是自动登录呢?直接访问successUI.jsp页面即可,这个页面是只有登录后才能访问的。http://localhost:8080/blogTest/cookie_successUI.action
这就是一个保存密码并且能够自动登录的案例了. 由于本人水平有限, 如有错误不当之处请各位不吝指出.

浙公网安备 33010602011771号