理解Cookie和Session机制 —— 通过Cookie实现登陆保持

前言

通过Cookie实现登陆保持的原理是在cookie中保存登陆的账号和一个经过加密的ssid来达到校验的目录,通过给该cookie设置超时时间,则可以设置登陆状态保持的时间,十分方便

基本流程

  1. 客户端发送登陆请求
  2. 服务端获取账号和ssid的cookie
  3. 如果获取到cookie并且校验通过则登陆成功并返回
  4. 否则,开始校验账号密码,如果校验失败则返回账号密码错误
  5. 到了这一步,说明cookie校验没通过,但是账号密码验证通过了
  6. 给resoponse设置返回的Cookie,包括账号和加密的ssid,返回前端
  7. 注销即是给账号和ssi的cookie设置超时时间为0,则浏览器会自动删除账号和ssid的cookie

效果

登陆页
在这里插入图片描述
登陆成功后,注销页
在这里插入图片描述

实现

废话不多说,直接上完整代码:

服务端代码

import com.test.common.PJCommon;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/loginDemo")
public class LoginController {

    private static final String KEY = "huzhenv5"; // 密钥

    /***
     * 测试登录
     * 这里假设用户(userName=admin,password=1111)
     */
    @RequestMapping(value = "/login.do", method = RequestMethod.POST)
    @ResponseBody
    public Map login(HttpServletRequest request, HttpServletResponse response) {
        Map<String,Object> retMap = new HashMap<String,Object>();
        Map<String,String> paramMap = PJCommon.getRequestParamMapAndSessionInfo(request);
        try {
            String account = paramMap.get("account").toString(); // 获取account参数
            String password = paramMap.get("password").toString(); // 获取password参数
            int timeoutStr = new Integer(paramMap.get("timeout").toString()); // 获取timeout参数

            // cookie超时时间
            int timeout = 0;
            // 判断有效期
            switch (timeoutStr) {
                case 0:
                    // 每次都需重新登陆
                    break;
                case 1:
                    // 关闭浏览器即失效
                    timeout = -1;
                    break;
                case 2:
                    // 30天内有效
                    timeout = 30 *24 * 60 * 60;
                    break;
                case 3:
                    // 永久有效
                    timeout = Integer.MAX_VALUE;
                    break;
            }

            // 开始进行登陆校验
            boolean islogin = false; // 是否登录
            String thisaccount = null; // 账号
            String thisssid = null; // SSID标识
            // 如果Cookie不为空,遍历Cookie
            if(request.getCookies() != null) {
                for(Cookie cookie : request.getCookies()) {
                    if(cookie.getName().equals("account"))
                        thisaccount = cookie.getValue();
                    if(cookie.getName().equals("ssid"))
                        thisssid = cookie.getValue();
                }
            }
            // 如果account、SSID都不为空,则进行校验是否已登陆
            if(thisaccount != null && thisssid != null) {
                islogin = thisssid.equals(calcMD1(thisaccount + KEY));
                // cookie校验通过,登陆成功
                retMap.put("state", 0);
                retMap.put("message", "Cookie登陆成功");
                return retMap;
            }
            // 安全验证cookie缺失,或者cookie校验未通过
            // 开始校验账号密码看是否正确
            if (!"admin".equals(account) || !"1111".equals(password)) {
                // 账号密码校验不通过
                retMap.put("state", -1);
                retMap.put("message", "账号/密码错误");
                return retMap;
            }
            // 账号密码验证通过,返回保持登陆的Cookie
            // 把账号、密钥使用MD1加密后保存
            String ssid = calcMD1(account + KEY);
            // 新建Cookie
            Cookie accountCookie = new Cookie("account", account);
            accountCookie.setMaxAge(timeout); // 设置有效期
            accountCookie.setPath("/sbs"); // 设置路径,如果不设置,默认为当前请求路径 /sbs/loginDemo
            accountCookie.setHttpOnly(true); // 设为true后,只能通过http访问,不能通过document.cookie获取,防止xss读取cookie
            // 新建Cookie
            Cookie ssidCookie = new Cookie("ssid", ssid);
            ssidCookie.setMaxAge(timeout); // 设置有效期
            ssidCookie.setPath("/sbs"); // 设置路径,如果不设置,默认为当前请求路径 /sbs/loginDemo
            ssidCookie.setHttpOnly(true); // 设为true后,只能通过http访问,不能通过document.cookie获取,防止xss读取cookie
            // 输出到客户端
            response.addCookie(accountCookie);
            response.addCookie(ssidCookie);

            // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容
//            response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());

            retMap.put("state", 0);
            retMap.put("message", "账号密码登陆成功");

        }catch(Exception e) {
            e.printStackTrace();
            retMap.put("state", -2);
            retMap.put("message", "登陆失败");
        }
        return retMap;
    }

    /***
     * 注销用户
     */
    @RequestMapping(value = "/logout.do", method = RequestMethod.POST)
    @ResponseBody
    public Map logout(HttpServletRequest request, HttpServletResponse response) {
        Map<String,Object> retMap = new HashMap<String,Object>();
        Map<String,String> paramMap = PJCommon.getRequestParamMapAndSessionInfo(request);

        try {
            // cookie account
            Cookie accountCookie = new Cookie("account", "");
            accountCookie.setMaxAge(0); // 设置有效期为0,删除
            accountCookie.setPath("/sbs");
            // cookie ssid
            Cookie ssidCookie = new Cookie("ssid", "");
            ssidCookie.setMaxAge(0); // 设置有效期为0,删除
            ssidCookie.setPath("/sbs");
            // 输出到客户端
            response.addCookie(accountCookie);
            response.addCookie(ssidCookie);

            // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容
//            response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());

            retMap.put("state", 0);
            retMap.put("message", "注销成功");

        } catch (Exception e) {
            e.printStackTrace();
            retMap.put("state", -2);
            retMap.put("message", "注销失败");
        }

        return retMap;
    }


    /**
     * md1加密算法
     * */
    public String calcMD1(String ss) { // MD1 加密算法
        String s = ss == null ? "" : ss; // 若为null返回空
        char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; // 字典
        try {
            byte[] strTemp = s.getBytes();                          // 获取字节
            MessageDigest mdTemp = MessageDigest.getInstance("MD5"); // 获取MD1
            mdTemp.update(strTemp);                                // 更新数据
            byte[] md = mdTemp.digest();                        // 加密
            int j = md.length;                                 // 加密后的长度
            char str[] = new char[j * 2];                       // 新字符串数组
            int k =0;                                         // 计数器k
            for (int i = 0; i< j; i++) {                       // 循环输出
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);                             // 加密后字符串
        } catch (Exception e){
            return null;
        }
    }

}

前端登陆页代码

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆</title>

    <script type="text/javascript" src="../../plugins/jquery/jquery-1.11.3.min.js"></script>
    <script type="text/javascript" src="login.js"></script>
</head>
<body>

<form>
    <table>
        <tr><td>账号: </td>
            <td><input id="accountInput" type="text"></td>
        </tr>
        <tr>
            <td>密码: </td>
            <td><input id="psInput" type="password"></td>
        </tr>
        <tr>
            <td>有效期: </td>
            <td id="timeRadios">
                <input type="radio" name="timeout" value="0" checked> 每次都需重新登陆 <br/>
                <input type="radio" name="timeout" value="1"> 关闭浏览器即失效 <br/>
                <input type="radio" name="timeout" value="2"> 30天内有效<br/>
                <input type="radio" name="timeout" value="3"> 永久有效<br/>
            </td>
        </tr>
        <tr>
            <td><input type="button" value=" 登  录 " class="button" onclick="doLogin()"></td>
        </tr>
    </table>
</form>

</body>
</html>

// 页面初始化方法
$(document).ready(function(){
    login(false);
});

function doLogin() {
    login(true);
}

/**
 * 登陆方法
 * @param showAlter 失败是否报错
 * */
function login(showAlter) {
    var account = $('#accountInput').val();
    var ps = $('#psInput').val();
    var timeout = $('#timeRadios > input:checked').val();
    // 请求
    $.ajax({
        type : 'POST',
        data: {
            account: account,
            password: ps,
            timeout: timeout
        },
        dataType: 'json',
        url : "../../loginDemo/login.do",
        success: function(res) {
            if (res.state == 0) {
                console.log(res.message);
                location.href = '../main/main.html'
            } else {
                showAlter && alert('失败:' + res.message);
            }
        },
        error: function(err) {
            showAlter && alert('错误:' + err.message);
        }
    })
}

前端注销页代码

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>xxx系统</title>

    <script type="text/javascript" src="../../plugins/jquery/jquery-1.11.3.min.js"></script>
    <script type="text/javascript" src="main.js"></script>
</head>
<body>

    <h1>Welcome to xxx 系统</h1>

    <input type="button" value=" 注  消 " class="button" onclick="logout()">

</body>
</html>
function logout() {
    $.ajax({
        type : 'POST',
        dataType: 'json',
        url : "../../loginDemo/logout.do",
        success: function(res) {
            if (res.state == 0) {
                location.href = '../login/login.html'
            } else {
                alert('失败:' + res.message);
            }
        },
        error: function(err) {
            alert('错误:' + err.message);
        }
    })
}

Git

该工程通过spring boot实现,完整的工程可以访问笔者的GitHub

GitHub链接:https://github.com/huzhen-v5/spring-boot-swagger
分支名:logindemo

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值