在莫斯科地铁的Wi-Fi中自动验证Android设备

如您所知,莫斯科地铁的几乎所有汽车都具有Wi-Fi接入点,用户可以使用这些接入点访问Internet,并愉快地从下班回家打通地铁的时间:阅读新闻,检查邮件,在YouTube上观看印章等。 。

每个设备必须先经过身份验证,然后才能被授予访问Internet的权限。首次向用户发送带有代码的SMS到指定的电话号码,此后系统会记住该设备的MAC地址,将来,为了进行身份验证,用户只需单击“输入Internet”链接,然后稍等一下。

这种系统的组织结构的缺点是,即使用户不需要浏览器,例如,他想使用专用应用程序进入邮件或阅读推特,他仍然需要启动浏览器,尝试访问某些页面,等待重定向,请点击链接,等待欢迎页面加载(可选:请参见商业广告),然后他才能使用所需的应用程序。

如果您不是地铁的常客,那么这种方案可能不会给您带来任何刺激,但是,如果每天使用,它仍然会令人不安,因此,正如一位著名且有超凡魅力的政治家所说:“足够忍受这一点!”,今天,我们将自动进行身份验证在莫斯科地铁。

首先,我们需要Tasker应用程序。您可以在这里(花很少的钱),或者在这里的某个地方(风险和风险自负)。就个人而言,我更喜欢第一种选择,并不后悔。

Tasker是一款允许根据特定条件(日期/时间/位置/设备状态/传感器读数等)执行某些操作(发送消息/显示通知/打开/关闭设备/渲染简单界面等)的应用程序。 d。)。条件和操作列表非常庞大,并且取决于Android的版本和设备的硬件,因此完全将它们带到没有意义。

因此,在启动Tasker之后,首先,您需要将界面翻译成英语,因为翻译的双腿都很la脚:“设置”->“界面”->“语言”->“英语”,然后重新启动应用程序。现在我们有四个选项卡:
  • 配置文件 -配置文件控制设备状态/各种事件和任务之间的连接;
  • 任务 -任务描述了必须执行的操作顺序;
  • 场景 -场景就像任务可以创建和自定义的自制表格,以及可以在其上运行任务的控件。
  • Vars-全局变量的列表,可用于在任务启动之间存储数据。


转到“ 任务”选项卡并创建一个新任务,将其命名为Metro Auth:



在打开的窗口中,我们首先需要定义几个变量。变量定义如下:


  • 变量名 - 变量名,必须以符号开头,并由小写字母组成。如果变量名至少包含一个大写字母,则该变量将变为全局变量,但我们不需要它;
  • To是变量的值。

因此,我们需要创建以下变量:
  • %url — , . — ( ). HTTPS , HTTP;
  • %forms — HTML-, . — 'auth-form,hidden_form', - , , ( );
  • %debug-此变量设置为非零值时,将导致显示其他调试信息,这将有助于我们制作上述形式的列表。

除了简单的操作,Tasker还使我们能够借助多种工具编写任意复杂的脚本。我们将使用简单的JavaScript:



在这里,您需要设置脚本的最大执行超时 -50秒,以防万一。自动退出”复选框负责在完成主脚本流程之后自动完成操作。如果使用异步请求(在我们的情况下)或setTimeout函数,则需要取消选中此框,并使用exit()函数自己确定操作是否完成

我将以两种格式设置来呈现脚本本身:如果要检查脚本而又不至于睁开眼睛,则需要适当的格式设置;格式化为窄屏后,脚本可以在手机的窄屏上看起来或多或少像样。最初,该脚本是在手机上以“窄版”键入的,然后才为文章重新格式化:

体面格式的脚本
function getUrl(url1,url2){
    url1=url1.split('?')[0];
    return url2.length?
        (/^http(s?):\/\//i.test(url2)?url2:
            (url2[0]=='/'?url1.split('/').slice(0,3).join('/')+url2:url1.split('/').slice(0,-1).join('/')+'/'+url2)
        ):url1;
}

function getVars(form,tag){
    vars='';
    fields=form.getElementsByTagName(tag);
    for(i=0;i<fields.length;i++)
        vars=vars+(i?'&':'')+fields[i].name+'='+fields[i].value;
    return vars;
}

function submit(xhr,request,form){
    request.url=getUrl(request.url,form.action);
    request.method=form.method;
    vars1=getVars(form,'input');
    vars2=getVars(form,'textarea');
    request.vars=vars1||vars2?(vars1?vars1:'')+(vars1&&vars2?'&':'')+(vars2?vars2:''):null;
    getPage(request,processPage,xhr);
}

function processPage(xhr,request){
    redir=xhr.getResponseHeader('Location');
    if(redir){
        if(redir==request.url) finalize(':  ');
        else{
            log('\n\n');
            getPage({'url':redir},processPage,xhr);
        }
    } else {
        forms=local('forms').split(',');
        id=null;
        for(i=0;i<forms.length;i++)
            if(xhr.response.getElementById(forms[i])) id=forms[i];
        if(id)submit(xhr,request,xhr.response.getElementById(id));
        else if(Number(local('debug'))){
            log('  :\n');
            forms=xhr.response.getElementsByTagName('form');
            if(forms.length)
                for(i=0;i<forms.length;i++)
                    log((i?', "':'"')+forms[i].id+'"');
            else log('');
            finalize();
        } else finalize(' ');
    }
}

function checkConn(xhr,request){
    redir=xhr.getResponseHeader('Location');
    if(redir){
        log('\n\n');
        getPage({'url':redir},processPage,xhr);
    } else {
        log('  ');
        finalize();
    }
}

function log(txt){
    logs=logs+(txt?txt:'');
}

function requestToText(request){
    return 'URL: '+request.url+'\nMethod: '+request.method+', Vars: '+request.vars+'\n\n';
}

function finalize(txt){
    log(txt);
    if(Number(local('debug'))) alert(logs);
    else if(txt) flashLong(txt);
    exit();
}

function getPage(request,func,xhr){
    if(!request.method) request.method='GET';
    if(!request.vars) request.vars=null;
    if(!xhr){
        xhr=new XMLHttpRequest();
        xhr.responseType="document";
        xhr.timeout=20*1000;
    }
    xhr.open(request.method,request.url,true);
    xhr.onload=function(){
        if(xhr.status==200 || xhr.status==401){
            log (requestToText(request)+'HTTP status: '+xhr.status+' '+xhr.statusText+'\n');
            func(xhr,request);
        } else {
            log(requestToText(request));
            finalize(' HTTP: '+xhr.status+' '+xhr.statusText);
        }
    }
    xhr.onerror=function(){
        log(requestToText(request));
        finalize(':  ');
    }
    xhr.ontimeout=function(){
        log(requestToText(request));
        finalize(':  ');
    }
    xhr.send(request.vars);
}

logs='';
getPage({'url':local('url')},checkConn);

窄屏格式的脚本
function getUrl(url1,url2){
  url1=url1.split('?')[0];
  return url2.length?
    (/^http(s?):\/\//i.test(url2)?
      url2:
        (url2[0]=='/'?
        url1.split('/').slice(0,3).join('/')+url2:
        url1.split('/').slice(0,-1).join('/')+
      '/'+url2)
    ):url1;
}

function getVars(form,tag){
  vars='';
  fields=form.getElementsByTagName(
    tag);
  for(i=0;i<fields.length;i++)
    vars=vars+(i?'&':'')+fields[i].name+
      '='+fields[i].value;
  return vars;
}

function submit(xhr,request,form){
  request.url=getUrl(request.url,
    form.action);
  request.method=form.method;
  vars1=getVars(form,'input');
  vars2=getVars(form,'textarea');
  request.vars=vars1||vars2?
    (vars1?vars1:'')+
    (vars1&&vars2?'&':'')+
    (vars2?vars2:'')
    :null;
  getPage(request,processPage,xhr);
}

function processPage(xhr,request){
  redir=xhr.getResponseHeader(
    'Location');
  if(redir){
    if(redir==request.url)
      finalize(':  '+
        '');
    else{
      log('\n\n');
      getPage({'url':redir},processPage,
        xhr);
    }
  } else {
    forms=local('forms').split(',');
    id=null;
    for(i=0;i<forms.length;i++)
      if(xhr.response.getElementById(
          forms[i]))
        id=forms[i];
    if(id)submit(xhr,request,
      xhr.response.getElementById(id));
    else if(Number(local('debug'))){
      log('  :\n');
      forms=xhr.response.
        getElementsByTagName('form');
      if(forms.length)
        for(i=0;i<forms.length;i++)
          log((i?', "':'"')+forms[i].id+'"');
      else log('');
      finalize();
    } else finalize(
      ' ');
  }
}

function checkConn(xhr,request){
  redir=xhr.getResponseHeader(
    'Location');
  if(redir){
    log('\n\n');
    getPage({'url':redir},processPage,
      xhr);
  } else {
    log('  '+
      '');
    finalize();
  }
}

function log(txt){logs=logs+(txt?txt:'');}

function requestToText(request){
  return 'URL: '+request.url+
    '\nMethod: '+request.method+
    ', Vars: '+request.vars+'\n\n';
}

function finalize(txt){
  log(txt);
  if(Number(local('debug'))) alert(logs);
  else if(txt) flashLong(txt);
  exit();
}

function getPage(request,func,xhr){
  if(!request.method)
    request.method='GET';
  if(!request.vars)request.vars=null;
  if(!xhr){
    xhr=new XMLHttpRequest();
    xhr.responseType="document";
    xhr.timeout=20*1000;
  }
  xhr.open(request.method,
    request.url,true);
  xhr.onload=function(){
    if(xhr.status==200 ||
        xhr.status==401){
      log (requestToText(request)+
        'HTTP status: '+xhr.status+' '+
        xhr.statusText+'\n');
        func(xhr,request);
    } else {
      log(requestToText(request));
      finalize(' HTTP: '+
        xhr.status+' '+xhr.statusText);
    }
  }
  xhr.onerror=function(){
    log(requestToText(request));
    finalize(':  '+
      '');
  }
  xhr.ontimeout=function(){
    log(requestToText(request));
    finalize(':  '+
      '');
  }
  xhr.send(request.vars);
}

logs='';
getPage({'url':local('url')},checkConn);

不幸的是,从电话键盘上键入脚本不利于注释,但我将简要描述该算法:
  1. 尝试加载%url变量中指定的页面
  2. 如果响应没有HTTP Location标头,则不会重定向我们,这意味着当前不需要身份验证,请退出
  3. 我们加载指向的页面。
  4. 如果有Location标头,请返回到步骤3
  5. 如果页面在%forms变量中具有列表中的表单,请显示其提交并返回步骤3
  6. 在其他情况下,我们成功通过了身份验证


完成脚本后,我们得到了以下任务:



使用底行的第一个图标,您可以尝试启动它。现在是时候乘地铁去设置它了!

在连接至接入点的地铁中,我们尝试启动任务。如果服务器可用性没有明显问题,那么我们将看到类似于下图所示的消息。在下面,我们看到了最后一个加载的页面上的表单标识符auth-form。此表单显然是我们的客户,我们将其名称引入变量%表单,然后再次运行任务,我们大致获得了下图中所示的内容。新的表单标识符为hidden_​​form。将其添加到%form变量中,现在其值将为' auth-form,hidden_​​form'。我们再次开始该任务,然后看到类似于下图所示的内容-要么是一个没有标识符的表格,要么是标记“不存在”(取决于地铁线路)。如果现在启动浏览器,很明显我们已经通过了身份验证。将变量%debug设置为“ 0”,然后关闭任务-至此完成。



现在,由您来配置在连接到所需的接入点时自动启动任务。我们转到“ 配置文件”选项卡并创建一个新的配置文件,配置文件在连接到莫斯科地铁的接入点后将被激活。在完成对接入点的描述之后,Tasker将会询问我们与此配置文件相关联的任务,当然,请选择Metro Auth



另一个细微差别:尽管很少发生,但身份验证仍然有效,尽管并未发生与点之间的断开连接。如果没有断开连接,就没有重新连接,这意味着Tasker将不会再次启动任务,因此我们将配置Tasker,以便每2分钟(最小可能的时间间隔)自动检查一次身份验证,为此,我们需要长时间单击已配置的条件以调用菜单,在其中添加用于设置间隔的临时条件。



这就是全部。从现在开始直到您必须更改%form变量中的标识符,进入笔架时的操作算法如下:
  1. 开启Wi-Fi;
  2. 等待屏幕上显示“身份验证完成”消息;
  3. 神秘地微笑着并开始您的生意。


UPD:关于建议 自我完美我导出了项目并将其放在一个文件中必须下载此文件并将其放在文件夹/ sdcard / Tasker / projects中,然后运行Tasker,长按左下角的房子图标以调出菜单并选择Import在此版本中,我每两分钟在一个单独的配置文件中签出一次-这应该更有效。

Source: https://habr.com/ru/post/zh-CN383109/


All Articles