源代码分析
博客园的登录页面非常简单,查看网页源代码,可以发现两个输入框的id分别为input1、input2,复选框的id为remember_me,登录按钮的id为signin。
还有一段JavaScript代码,下面来简单分析一下。
先来看$(function(){});函数:
1 $(function () { 2 $(‘#signin‘).bind(‘click‘, function () { 3 signin_go(); 4 }).val(‘登 录‘); 5 });
$(function(){});是$(document).ready(function(){})的简写。当页面加载完成之后,$(function(){})里的代码就会被执行。
$(‘#signin‘)表示选取id为signin的元素,即登录按钮。
bind()方法为被选元素添加一个或多个事件处理程序,并规定事件发生时运行的函数。
val()方法返回或设置被选元素的值。
当点击登录按钮时,将执行signnin_go()函数。
JSEncrypt是一个用于RSA加密的库。在signnin_go()函数中,通过JSEncrypt对用户名和密码进行了加密,setPublicKey()函数里面就是加密的公钥。
$(‘#remember_me‘).prop(‘checked‘)返回复选框的状态,勾选时返回true,否则返回false。
两段加密后的密文和复选框的状态被保存在一个名为ajax_data的对象里。代码如下:
1 var encrypt = new JSEncrypt(); 2 encrypt.setPublicKey(‘MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp0wHYbg/NOPO3nzMD3dndwS0MccuMeXCHgVlGOoYyFwLdS24Im2e7YyhB0wrUsyYf0/nhzCzBK8ZC9eCWqd0aHbdgOQT6CuFQBMjbyGYvlVYU2ZP7kG9Ft6YV6oc9ambuO7nPZh+bvXH0zDKfi02prknrScAKC0XhadTHT3Al0QIDAQAB‘); 3 var encrypted_input1 = encrypt.encrypt($(‘#input1‘).val()); 4 var encrypted_input2 = encrypt.encrypt($(‘#input2‘).val()); 5 var ajax_data = { 6 input1: encrypted_input1, 7 input2: encrypted_input2, 8 remember: $(‘#remember_me‘).prop(‘checked‘) 9 };
然后就是最主要的$.ajax({})函数了,ajax()方法通过HTTP请求加载远程数据,在不刷新页面的情况下与服务器交换数据,返回其创建的 XMLHttpRequest 对象。详情请参考jQuery ajax - ajax() 方法
1 $.ajax({ 2 url: ajax_url, 3 type: ‘post‘, 4 data: JSON.stringify(ajax_data), 5 contentType: ‘application/json; charset=utf-8‘, 6 dataType: ‘json‘, 7 headers: {‘VerificationToken‘: ‘。。。省略,此处是一大串字符。。。‘}, 8 success: function (data) { 9 if (data.success) { 10 $(‘#tip_btn‘).html(‘登录成功,正在重定向...‘); 11 location.href = return_url; 12 } else {} 13 }, 14 error: function (xhr) {} 15 });
上面的代码中做了一些省略。需要特别注意的是headers属性,刷新之后,VerificationToken的值将会改变,所以在模拟登录时,必须先获取这一段字符串。登录成功后,页面将进行跳转。
抓包分析
这是登录时的POST请求,其中有3个参数要特别注意:
VerificationToken就是上文中提到的一段变化的字符串。
Ajax异步请求比传统的同步请求多了一个头参数,即X-Requested-With。
Content-Type用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些Asp网页点击的结果却是下载到的一个文件或一张图片的原因。在这里返回的是json格式的数据。当输入错误的用户名和密码时,将得到下面的json数据:
正确的用户名和密码将得到{"success":true}
模拟登录
1 import requests 2 import json 3 login_url=‘https://passport.cnblogs.com/user/signin‘ 4 return_url=‘https://home.cnblogs.com/u/-E6-/‘#我的主页 5 session=requests.session() 6 7 #获取VerificationToken 8 login_page=session.get(login_url) 9 p=login_page.text.find(‘VerificationToken‘)+len(‘VerificationToken‘)+4 10 token=login_page.text[p:login_page.text.find("‘",p)] 11 12 headers={‘Content-Type‘: ‘application/json; charset=UTF-8‘, 13 ‘X-Requested-With‘: ‘XMLHttpRequest‘, 14 ‘VerificationToken‘:token} 15 session.headers.update(headers) 16 #可以通过抓包获取加密后的用户名和密码,我将在附录部分介绍如何在Python中加密 17 data={"input1":"。。。省略。。。", 18 "input2":"。。。省略。。。", 19 "remember":True} 20 21 #模拟登录 22 response=session.post(login_url,data=json.dumps(data)) 23 print(response.text) 24 #{"success":true} 25 #登录成功 26 27 #跳转到主页 28 home_page=session.get(return_url) 29 #获取主页标题 30 p=home_page.text.find(‘<title>‘)+len(‘<title>‘) 31 title=home_page.text[p:home_page.text.find(‘</title>‘,p)] 32 print(title) 33 #E6的主页 - 博客园 34 #如果登录失败,跳转到主页时返回的结果没有title标签,home_page.text将为‘需要登陆‘
附录:在Python中进行RSA加密
在python中加密需要Crypto和base64
from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 from base64 import b64encode
在网页源代码中可以直接找到公钥(位于setPublicKey()函数里):MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp0wHYbg/NOPO3nzMD3dndwS0MccuMeXCHgVlGOoYyFwLdS24Im2e7YyhB0wrUsyYf0/nhzCzBK8ZC9eCWqd0aHbdgOQT6CuFQBMjbyGYvlVYU2ZP7kG9Ft6YV6oc9ambuO7nPZh+bvXH0zDKfi02prknrScAKC0XhadTHT3Al0QIDAQAB
标准的公钥格式为
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNPaXE9SARgvD7l5FgU5B/ibE/ Uuu4okbt6LGzXVYtx1tzgdnV9/BiDgauRsWGjofo0o3+cVLs16hUdRJ9BoAr0jL8 00AKy9rkcOi0lJI8XBZrtX2Ad+uwf4kLNjL2MkLkSbhtwRzpiAFcjMrhyOi6y/0c KafXI3SXOgVBA5w2dQIDAQAB -----END PUBLIC KEY-----
每行64个字符。
1 #标准的公钥格式,bytes类型,每64字符换一次行 2 key_bytes=b"-----BEGIN PUBLIC KEY-----\n 3 MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp0wHYbg/NOPO3nzMD3dndwS0M\n 4 cc\uMeXCHgVlGOoYyFwLdS24Im2e7YyhB0wrUsyYf0/nhzCzBK8ZC9eCWqd0aHbd\n 5 gOQT6CuFQBMjbyGYv\lVYU2ZP7kG9Ft6YV6oc9ambuO7nPZh+bvXH0zDKfi02prk\n 6 nrScAKC0XhadTHT3Al0QIDAQAB\n 7 -----END PUBLIC KEY-----" 8 #生成Crypto.PublicKey.RSA._RSAobj类型的对象 9 publickey=RSA.importKey(key_bytes) 10 #构造“加密器” 11 encryptor=PKCS1_v1_5.new(publickey) 12 #加密的内容必须为bytes类型 13 username=b‘123‘ 14 password=b‘abc‘ 15 #加密,并将结果转换成字符串 16 input1=str(b64encode(encryptor.encrypt(username)),‘utf-8‘) 17 input2=str(b64encode(encryptor.encrypt(password)),‘utf-8‘) 18 19 data={‘input1‘:input1,‘input2‘:input2,‘remember‘:True}