zoukankan      html  css  js  c++  java
  • 浅析博客园的保存密码并自动登录, 然后自己写一个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>
    View Code

      这里也涉及到了用js操作cookie    

      successUI.jsp

    <body>
        登陆后才能看见我
      </body>
    View Code

      后台是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>
    View Code

      

      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;
        }
    View Code

      我的表示本来就存在的表, 里面有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() {
        }
    }
    View Code

      

      用于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;
        }
    }
    View Code

      

      以上是这个案例的主要代码, 下面把一些其他用到的代码也贴到下面

      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;
        }
    }
    View Code

      

      操作数据库用的是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;
        }
    }
    View Code

      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;
        }
    }
    View Code

      

      当然不要忘了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>
    View Code

       

      第一次登录时在登录页面进行登录http://localhost:8080/blogTest/cookie_loginUI.action

      下次再打开登录页面时,页面会自动从cookie中取出用户名并填写在文本框中。怎么证明是自动登录呢?直接访问successUI.jsp页面即可,这个页面是只有登录后才能访问的。http://localhost:8080/blogTest/cookie_successUI.action

      这就是一个保存密码并且能够自动登录的案例了. 由于本人水平有限, 如有错误不当之处请各位不吝指出.

  • 相关阅读:
    json web token 入门
    Mysql查询表注释和字段注释信息
    Nginx核心知识100讲学习笔记(陶辉):目录
    Kubernetes进阶实战读书笔记:网络存储
    Kubernetes进阶实战读书笔记:持久化存储卷(pv详解)
    Kubernetes进阶实战读书笔记:存储卷概述
    sybase
    Delphi 解决StrToDateTime()不是有效日期类型的问题
    delphi TStringList 用法详解
    看看Delphi中的列表(List)和泛型
  • 原文地址:https://www.cnblogs.com/pdzbokey/p/6248241.html
Copyright © 2011-2022 走看看