前端登陆之cookie篇

Posted 遛狗先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端登陆之cookie篇相关的知识,希望对你有一定的参考价值。

一、cookie

上一个文章介绍了实用node写了一个服务,以及调用api去操作数据库的一个前后端分离的demo,这次重点记录一下前后端登陆操作的一个部分。登陆认证是一个项目中比较重要的部分,接下来我将使用cookie的模式来实现登陆认证!

cookie的组成

cookie是浏览器中特有的一个概念,它就像浏览器的专属卡包,管理着各个网站的身份信息。

每个cookie就相当于是属于某个网站的一个卡片,它记录了下面的信息:

  • key:键,比如「身份编号」

  • value:值,比如张博的身份编号「14563D1550F2F76D69ECBF4DD54ABC95」,这有点像卡片的条形码,当然,它可以是任何信息

  • domain:域,表达这个cookie是属于哪个网站的,比如123.com,表示这个cookie是属于123.com这个网站的

  • path:路径,表达这个cookie是属于该网站的哪个基路径的,就好比是同一家公司不同部门会颁发不同的出入证。比如/news,表示这个cookie属于/news这个路径的。(后续详细解释)

  • secure:是否使用安全传输

  • expire:过期时间,表示该cookie在什么时候过期

当浏览器向服务器发送一个请求的时候,它会瞄一眼自己的卡包,看看哪些卡片适合附带捎给服务器

如果一个cookie同时满足以下条件,则这个cookie会被附带到请求中

  • cookie没有过期

  • cookie中的域和这次请求的域是匹配的

  • cookie是不在乎端口的,只要域匹配即可

  • cookie中的path和这次请求的path是匹配的

  • 比如cookie中的path是/news,则可以匹配的请求路径可以是/news、/news/detail、/news/a/b/c等等,但不能匹配/blogs

  • 如果cookie的path是/,可以想象,能够匹配所有的路径

  • 验证cookie的安全传输

  • 如果cookie的secure属性是true,则请求协议必须是https,否则不会发送该cookie

  • 如果cookie的secure属性是false,则请求协议可以是http,也可以是https

如果一个cookie满足了上述的所有条件,则浏览器会把它自动加入到这次请求中

具体加入的方式是,浏览器会将符合条件的cookie,自动放置到请求头中。

如果把它用于登录场景,就是如下的流程:

登录请求

  1. 浏览器发送请求到服务器,附带账号密码

  1. 服务器验证账号密码是否正确,如果不正确,响应错误,如果正确,在响应头中设置cookie,附带登录认证信息(至于登录认证信息是设么样的,如何设计,要考虑哪些问题,就是另一个话题了,可以百度 jwt)

  1. 客户端收到cookie,浏览器自动记录下来

后续请求

  1. 浏览器发送请求到服务器,希望添加一个管理员,并将cookie自动附带到请求中

  1. 服务器先获取cookie,验证cookie中的信息是否正确,如果不正确,不予以操作,如果正确,完成正常的业务流程

二、处理登陆APi接口

接下来就是处理登陆这个api接口:

const express = require("express")
const router = express.Router()
const user = require("../../serveAPI/userService")
const asyncHandler = require("../getResult")
const cryptor = require("../../util/crypt")

router.post("/login",asyncHandler(
    async (req,res,next) =>
    const result = await user.login(req.body.loginId,req.body.loginPwd)
    if (result) 
        //如果result存在则登录成功
        //设置cookie
        //给浏览器用cookie

        //cookie-parse给了一个默认加密,其中有默认密钥可以设置
        const value = result.id
        cryptor.encrypt(value)

        res.cookie("token",result.id,
            path:"/",
            domain:"localhost",
            maxAge:3600*24*10000,
            // signed:true,
        )
        //给其他应用 如手机端
        res.header("authorization",value)

      
      return result;
    
))

当我们调用这个接口之后,就会从数据库中访问这个用户(根据用户名和密码),这个密码时使用md5进行加密,然后解密的:

当返回这个结果的时候,我们就使用cooke方法进行给res请求结果中设置cookies,将token设为用户的id,剩下的domain,还有path,maxAge都可以视情况而定,但是这里的id就可以在浏览器端使用document.cookie获取到,然后提取出来id,我们在服务端,也可以设置让js不能获取cookie,防止跨站脚本攻击,但是还是不安全,因为这个数据是明文的,可以在浏览器中的cookie中看到数据,而cookie-parse里面提供一个自动加密的属性,就是signed为true,就会给这个cookie里的token值自动加密,之后取到也可以自动解密,但是有个很严重的问题,这里cookie是浏览器特有的一个属性,在其他端虽然可以使用但是没有像浏览器这样专门设置这个cookie模块,来处理请求,所以我们在这个请求头中,留一手,给请求头中再设置一个authorization的值为用户id,这样不管是pad端,手机端都可以处理这个请求内容,所以当设置这个authorization这个键之后,cookie-parse只能去处理这个cookie中的token而不能处理authorization,所以加密这个事情还得我们来做,这里使用node里面提供的这个crypto模块,来进行加密:


const secret = Buffer.from("mm7h3ck87ugk9l4a");
const crypto = require("crypto");
const iv = Buffer.from("jxkvxz97409u3m8c");

//加密一个字符串
exports.encrypt = function (str) 
  const cry = crypto.createCipheriv("aes-128-cbc", secret, iv);
  let result = cry.update(str, "utf-8", "hex");
  result += cry.final("hex");
  return result;
;


//解密一个字符串
exports.decrypt = function (str) 
  const decry = crypto.createDecipheriv("aes-128-cbc", secret, iv);
  let result = decry.update(str, "hex", "utf-8");
  result += decry.final("utf-8");
  return result;
;

上面是一个js文件导出两个函数,一个用于加密,一个用语解密;

使用对称加密算法:aes算法 128位密钥(16个字节的字符串),其中mm7h3ck87ugk9l4a是用随机数转字符串拼接获取Math.random(),toString(36).slice(-8)+Math.random(),toString(36).slice(-8)

Buffer是node里面的一个类,JS里的String对象,存储的是字符串,而且是Unicode编码的。

我们使用node内置库crypto,crypto.getCiphers()可以看到crypto所有的加密函数,而我们使用第一个aes-128-cbc,接下来是准备一个iv,随机向量 ,一般来说密钥固定,向量是不固定的,这里我为了简便实用固定的。而这个密钥是存储在服务端的,所以很难进行解密,这样就会让数据有安全保障。第一层保障是这个密钥,如果这个密钥泄漏,下一个保障就是iv向量,这里写的固定的,当然也可以写那种不固定的,使用到js中的闭包;

module.exports = function()
  //准备一个iv
  const iv = Buffer.from(
    Math.random(),toString(36).slice(-8)+Math.random(),toString(36).slice(-8)
  );
  return 
    encrypt(str)
      const cry = crypto.createCipheriv("aes-128-cbc", secret, iv);
      let result = cry.update(str, "utf-8", "hex");
      result += cry.final("hex");
      return result;
    ,
    decrypt(str)
      const decry = crypto.createDecipheriv("aes-128-cbc", secret, iv);
      let result = decry.update(str, "hex", "utf-8");
      result += decry.final("utf-8");
      return result;
    ,
  

这里我们直接导出一个函数,这个函数执行后就会给予一个对象,对象有两个方法,第一个是加密,第二个是解密,而这两个函数都使用到函数里定义的变量iv,当我们调用一次这个函数,就会生成一个iv,所以这个iv是不固定的,也是无法获取的,这样就完善了token加密的一个步骤,让数据更加的安全!

三、使用中间件获取cookie

我们使用cookie-parser插件写入中间件,之后再每一次的请求中都会在req对象中注入cookies属性,用于获取传递过来的cookie,也会在res对象中注入获取cookie的方法,所以操作起来就更加方便。

我们引入自己写的cookieMiddleware中间件

/**
 * 用于解析token
 */
const  getErr  = require("./getResult");
const cryptor = require("../util/crypt")

// 用于解析token
module.exports = (req, res, next) => 
  let token = req.cookies.token;

// 获取cokkie
// let token = req.signedCookies.token;

  if(!token)
    //其他的端获取token
    token = req.headers.authorization
  

  if(!token)
    handleNonToken(req,res,next)
    return;
  
  const userId = cryptor.decrypt(token)
  console.log(userId,"认证通过啦~~~");
  next()
;

//处理没有认证的情况
function handleNonToken(req, res, next) 
  res
    .status(403)
    .send(getErr("不好意思,您没有token令牌无法登陆哦~", 403));

以上可以看出,当我们调用接口的时候,就会进行token认证,首先从req中获取cookie如果没有的话,就会在authorization中获取,如果都没有那么就报错,如果有一个有,就进行解密,然后执行下一个中间件,至此,使用cookie方式登陆的模式完成!

Python爬虫个人记录利用Python在豆瓣上写一篇日记

涉及关键词:requests库 requests.post方法 cookies登陆

version 1.5(附录):使用post方法登陆豆瓣,成功! 缺点:无法获得登陆成功后的cookie,要使用js等方法来获得cookie,放弃

versoin 2.0(附录):  使用selenium模拟浏览器登陆豆瓣,使用浏览器自动加载js,并成功获取cookies,可以为后来浏览使用,可行,成功!

 

一、目的分析

利用cookie登陆豆瓣,并写一篇日记 
https://www.douban.com/note/636142594/

二、步骤分析

1、使用浏览器登陆豆瓣,得到并分析cookie

2、使用cookie模拟登陆豆瓣(使用账号密码登陆也可以,需要验证码,cookie的时效一般就几天)

3、分析浏览器写日记行为,在python中模拟post行为

4、源码及测试

三、scrapy shell 模拟登陆

1、使用浏览器登陆豆瓣,在fidder中获得cookie

cookie中有许多项(并不是全部需要),经过一条条测试,发现只要包含\'dbcl2\'就能登录

2、打开scrapy shell 测试登陆

  模拟浏览器User-Agent和cookies

$ scrapy shell
...
from scrapy import Request
cookies = {\'dbcl2\':\'"164753551:kjyoTNgwwII"\'}
headers={\'User-Agent\':\'Mozilla/5.0\'}
req = Request(\'https://www.douban.com/mine/\', headers=headers,cookies = cookies)
fetch(req)
#使用浏览器检查元素得到xpath(方法参考爬虫(一)(二))(日记内容权限未自己可见,若可看见日记内容便模拟登陆成功)
>>> response.xpath(\'//*[@id="note_636142594_short"]\').extract()
[\'<div class="note" id="note_636142594_short">Hello Douban</div>\']
>>> response.xpath(\'//*[@id="note_636142594_short"]/text()\').extract()
[\'Hello Douban\']
>>>

得到日记内容,可见模拟登陆成功,cookie可用

四、python 写豆瓣日记

1、使用浏览器写日记,并在fidder中观察行为

 

 

 

发现浏览器进行了POST https://www.douban.com/note/create HTTP/1.1的行为

post 的内容是ck=BsJH&note_id=636142544&note_title=test_2&note_text=hello2&author_tags=&note_privacy=P

ck=BsJH  是cookie中的一个值 

note_id=636142544(估计是用户id,直接照抄)

note_id=636142544&note_title=test_2&note_text=hello2(标题,以及内容)

另外三个参数不重要,使用默认就行

 

2、使用python模拟post行为

#post 所需要的参数

requests.post(url = url,data = data,headers=headers,verify=False,cookies = cookies)

五、源码及测试

源码
 1 import requests
 2 ### 1、首先登陆任何页面,获取cookie
 3 
 4 #使用requests打开https时会产生warming,加上这句屏蔽
 5 requests.packages.urllib3.disable_warnings()
 6 
 7 headers = dict()
 8 headers[\'User-Agent\'] = \'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.3387.400 QQBrowser/9.6.11984.400\'
 9 
10 cookies = dict()
11 cookies = {#\'ll\':\'"118201"\',
12             #\'bid\':\'PUWFXi53MHA\',
13             #\'_ga\':\'GA1.2.1759080547.1501749204\',
14             #\'__yadk_uid\':\'rjmLGzYjJuHI5lHNHJX3lOgBAltgb5Xy\',
15             #\'gr_user_id\':\'16c2c492-9e32-4af2-9c35-230e8d43db06\',
16             #\'ps\':\'y\',
17             #\'_pk_ref.100001.8cb4\':\'%5B%22%22%2C%22%22%2C1504529257%2C%22https%3A%2F%2Faccounts.douban.com%2Flogin%3Fredir%3Dhttps%253A%252F%252Fwww.baidu.com%252Flink%253Furl%253DEh3nGSbWZ6S0P2OQc7QHrEzCkdwJewBLjFnBpRTRwKv4QwoLScCwKCSh9iQFeDAx%2526wd%253D%2526eqid%253D8191d1c1000627560000000359ad43f4%22%5D\',
18             #\'ap\':\'1\',
19             #\'_vwo_uuid_v2\':\'57D26B154CE7E363177CFD5F35F06F34|e63fa1bfe4c07598b6454ae2a97166cb\',
20             \'dbcl2\':\'"164753551:kjyoTNgwwII"\'
21             #\'ck\':\'osar\',
22             #\'_pk_id.100001.8cb4\':\'70e88acbc88cb16d.1501749196.11.1504530290.1504527380.\',
23             #\'_pk_ses.100001.8cb4\':\'*\',
24             #\'push_noty_num\':\'0\',
25             #\'push_doumail_num\':\'0\',
26             #\'__utma\':\'30149280.1759080547.1501749204.1504529257.1504530054.20\',
27             #\'__utmb\':\'30149280.5.10.1504530054\',
28             #\'__utmc\':\'30149280\',
29             #\'__utmz\':\'30149280.1504530054.20.16.utmcsr\',
30             #\'__utmv\':\'30149280.16475\'
31             }
32 
33 data = {\'ck\':\'BsJH\',
34         \'note_id\':\'636142544\',
35         \'note_title\':\'HelloPython\',
36         \'note_text\':\'HelloPython\'
37         #\'author_tags\':\'\',
38         #\'note_privacy\':\'P\'
39     }
40 url = \'https://www.douban.com/note/create\'
41 #注意访问https链接时要加上verify=False参数,否则回报错
42 ret = requests.post(url = url,
43                     data = data,
44                     headers=headers,
45                     verify=False,
46                     cookies = cookies
47                     )
48 print(ret.text[:300])
49 print(ret.cookies.get_dict())
View Code

测试结果

大功告成!

五、总结分析

1、这次使用cookie登陆免去了验证码麻烦,下次希望能研究验证码的破解

2、cookie的使用时间有限,隔一段时间就要更换

3、requests对https的限制挺严格的,需要加入verify=False,并且要屏蔽警告信息

  #使用requests打开https时会产生warming,加上这句屏蔽
  requests.packages.urllib3.disable_warnings()

 

附录(一)

vesion 1.5

import requests
from lxml import etree
import time

#使用requests打开https时会产生warming,加上这句屏蔽
requests.packages.urllib3.disable_warnings()

def get_github_html(url):
    \'\'\'
    这里用于获取登录页的html,以及cookie
    :param url: https://github.com/login
    :return: 登录页面的HTML,以及第一次的cookei
    \'\'\'
    response = requests.get(url,verify=False)
    first_cookie = response.cookies.get_dict()
    return response.text,first_cookie

def get_token(html,xrule):
    \'\'\'
    处理登录后页面的html
    :param html:
    :return: 获取csrftoken
    \'\'\'   
    selector = etree.HTML(html)
    token = selector.xpath(xrule)[0]
    #print(token)

    return token


def gihub_login(url,cookie):
    \'\'\'
    这个是用于登录
    :param url: https://github.com/session
    :param token: csrftoken
    :param cookie: 第一次登录时候的cookie
    :return: 返回第一次和第二次合并后的cooke
    \'\'\'

    data={
        "source":"movie",
        "redir":"https://movie.douban.com/chart",  
        "form_email":"***********",  
        "form_password":"***********",  
        "login":u\'登录\',
        "remember":"on"
    }
    response = requests.post(url,data=data,cookies=cookie,verify=False)
    print(response.status_code)
    print(response.url)
    print(response.cookies.get_dict())
    cookie2 = response.cookies.get_dict()
    #这里注释的解释一下,是因为之前github是通过将两次的cookie进行合并的
    #现在不用了可以直接获取就行
    cookie.update(cookie2)
    print(cookie)
    #print(response.text)
    return cookie


if __name__ == \'__main__\':
    Base_URL = "https://movie.douban.com/?_t_t_t=0.6509884103763016"
    Login_URL = "https://www.douban.com/accounts/login"
    html,cookie = get_github_html(Base_URL)
    print(cookie)
    #xrule = \'//*[@id="login"]/form/div[1]/input[2]/@value\'
    #token = get_token(html,xrule)
    #print(token)
    time.sleep(3)
    cookie2 = gihub_login(Login_URL,cookie)
    time.sleep(3)
    response = requests.get("https://www.douban.com/mine/",verify=False,cookies=cookie2)
    print(response.cookies.get_dict())
    print(response.url,response.status_code)
    #print(response.text)
vesion 1.5

vesion 2.0

from selenium import webdriver

Base_URL = "https://movie.douban.com/?_t_t_t=0.6509884103763016"
Login_URL = "https://www.douban.com/accounts/login"

browser = webdriver.Firefox()
browser.get(\'https://movie.douban.com/\')
cookies = browser.get_cookies()
#print(cookies)

#打开网址
browser.get(\'https://www.douban.com/accounts/login\')
#browser.maximize_window()#窗口最大化,可有可无,看情况

#输入账户密码
#我请求的页面的账户输入框的\'id\'是username和密码输入框的\'id\'是password
browser.find_element_by_id(\'email\').clear()
browser.find_element_by_id(\'email\').send_keys(u\'***********\')
browser.find_element_by_id(\'password\').clear()
browser.find_element_by_id(\'password\').send_keys(u\'***********\')

#remember me
browser.find_element_by_id(\'remember\').click()

#输入完用户密码当然就是提交了,通过\'name\'为login来找到提交按钮
browser.find_element_by_name(\'login\').click()

#print(browser.current_url)

if browser.current_url ==\'https://www.douban.com/accounts/login\':
    #输入账户密码
    #我请求的页面的账户输入框的\'id\'是username和密码输入框的\'id\'是password
    browser.find_element_by_id(\'email\').clear()
    browser.find_element_by_id(\'email\').send_keys(u\'***********\')
    browser.find_element_by_id(\'password\').clear()
    browser.find_element_by_id(\'password\').send_keys(u\'***********\')
    captcha_field = input("请输入验证码:")
    captcha_field = str(captcha_field)
    browser.find_element_by_id(\'captcha_field\').clear()
    browser.find_element_by_id(\'captcha_field\').send_keys(captcha_field)
    #remember me
    browser.find_element_by_id(\'remember\').click()
    #输入完用户密码当然就是提交了,通过\'name\'为login来找到提交按钮
    browser.find_element_by_name(\'login\').click()
    

browser.get("https://www.douban.com/mine/")

cookies2 = browser.get_cookies()
print(browser.current_url)
print(cookies2)

#浏览器退出
browser.quit()
vesion 2.0

 

 



 

以上是关于前端登陆之cookie篇的主要内容,如果未能解决你的问题,请参考以下文章

java后台怎么获取前台用户登录信息 包括 ip 登录时间

前端登陆实现

前端之HTML篇

项目实战之本地存储篇

web前端面试题@十九(怎么判断用户是不是处于登陆状态?)

JAVA面试之互联网经验篇