神代綺凜

[Pixiv] 纯 Nginx 反代P站实录及要点解析
解决了一年前的我无法解决的世纪难题(x 深刻意识到,很多以前自己认为无解的问题并不是真的无解,只是自己的知识面不够...
扫描右侧二维码阅读全文
08
2018/11

[Pixiv] 纯 Nginx 反代P站实录及要点解析

解决了一年前的我无法解决的世纪难题(x

深刻意识到,很多以前自己认为无解的问题并不是真的无解,只是自己的知识面不够而无法意识到问题的核心所在罢了。

总结起来就一个字:菜

Head Pic: 「Halloween」/「鳥成」のイラスト [pixiv]

反代P站

为什么要折腾这个?

一是觉得好玩(。),二是由于 SNI 阻断导致即使改 Hosts 解决了 DNS 污染问题也无法直连P站,三是可以给身边需要的朋友用。

准备工作

  1. 一台没被P站屏蔽的主机
    众所周知 Vultr 大部分IP段都被P站屏蔽
  2. 一个船新的域名
    其实随意啦,用自己域名的二级来弄也可,只是域名会变得比较长
    后续均以example.com来指代我们使用的域名,请灵性代换
  3. 使用“不需要通过验证站点文件来签发/续签”并且最好还支持泛域名的 SSL 证书
    这里我们自然首推 Let's Encrypt,这是看起来唯一符合所有需求并且还免费的证书

域名及证书

需要使用哪些域?

在反代时我们需要用到以下几个域

如果你愿意使用一个船新域名专门反代:

example.com
*.example.com
*.pximg.example.com
  • example.com
    随意,你可以放点自己的东西做一些伪装或者说明,或者直接 301 到www.example.com
  • *.example.com
    用于反代对齐*.pixiv.net
  • *.pximg.example.com
    用于反代对齐*.pximg.net,其实该域名中的pximg也可以替换成其他的字符串,只要不与P站的二级域名服务产生冲突即可

如果你想用一个自己正在使用的域名反代并且不想影响该域名的其他服务:

pixiv.example.com
*.pixiv.example.com
*.pximg.example.com

各自作用同上,在后续配置上灵性修改即可

获取证书

使用 acme 的 DNS API 方式进行挑战验证来签发证书是最方便的

参考以下文章

在配置好 API 之后我们使用这样的命令即可签发一个我们想要的三域名合一的证书了,并且还能自动续签,岂不美哉

~/.acme.sh/acme.sh --issue --dns dns_cx -d example.com -d '*.example.com' -d '*.pximg.example.com'

Nginx

不要使用 Tengine 等 Nginx 分支版本,在某些模块上可能会有一些奇怪的差异与问题

基础配置

这里仅列出关键配置,通常配置例如listenexpires以及 SSL 之类的不会写出,自行添加

# *.example.com
server
{
    server_name ~^([^.]+)\.example\.com$;
    set $domain $1;

    resolver 8.8.8.8;

    location ~ .*
    {
        proxy_set_header Host $domain.pixiv.net;
        proxy_cookie_domain pixiv.net example.com;
        proxy_pass https://$domain.pixiv.net;
        proxy_set_header Accept-Encoding "";

        sub_filter "pixiv.net" "example.com";
        sub_filter "pximg.net" "pximg.example.com";
        sub_filter_once off;
        sub_filter_types *;
    }
}

# *.pximg.example.com
server
{
    server_name ~^([^.]+)\.pximg\.example\.com$;
    set $domain $1;

    resolver 8.8.8.8;

    location ~ .*
    {
        proxy_set_header Host $domain.pximg.net;
        proxy_set_header Referer "https://www.pixiv.net";
        proxy_pass https://$domain.pximg.net;
        proxy_set_header Accept-Encoding "";

        sub_filter "pixiv.net" "example.com";
        sub_filter "pximg.net" "pximg.example.com";
        sub_filter_once off;
        sub_filter_types *;
    }
}

详解

  • server_nameset
    使用正则表达式匹配以方便直接提取出我们要反代的二级域名
  • resolver
    必要,指定域名解析所用 DNS,因为在后续proxy_pass中我们要反代的域名是由$domain决定,本身是不定的,Nginx 必须被指定 DNS 才能处理域名解析
  • proxy_cookie_domain
    改变反代后返回的 header 中 set-cookie 里 cookie 对应的域名,只在*.example.com中需要,是解决登陆问题的关键,如想了解后续文章会解释
  • proxy_set_header Referer
    设置 header 中的 Referer,只在*.pximg.example.com中需要,目的是解决i.pximg.net的防盗链问题
  • proxy_set_header Accept-Encoding
    将接受的压缩编码设为空,即不接受压缩,因为sub_filter无法对压缩过的内容起效
  • sub_filter
    将反代后得到的内容进行字符串替换,以保证链接域名等与反代域名一致
  • sub_filter_types
    必须设置为*,否则默认对于 API 返回的 json 内容等不会进行替换,会导致依靠 ajax 运作的一些功能的异常

局限性

  1. 不能使用绑定的社交账号的登录方式
  2. 广告显示不出(这算哪门子局限性x),因为广告是另一个域名所以懒得再开一个证书了

可能存在尚未发现的问题,不过常用的功能都是没有问题的

解决登陆问题的关键

这个问题困扰我一年了,以前我尝试反代的时候,登录会提示“无效的服务器”而无法登陆,直到最近才发现真正原因

我通过搜索引擎查找了很多文章,直到遇见了这篇
模拟登录pixiv.net后续 - ことりのおやつにしてやるぞー!

之前我认为,登录时唯一要注意的只有postKey,而代码中的获取 cookie 的操作提醒了我,我的直觉告诉我登录失败是跟 cookie 有关

于是我又翻看了一下 GET 登陆页面时的 header,原来是有 set-cookie 的,而我之前一直从登录相关 js 的操作入手因此没有注意到这个细节

根据这个思路我试着自己用 Nodejs 写了个模拟登录来尝试

const Axios = require('axios');
const Qs = require('qs');

async function test() {
    let postKey, cookie = '';

    //访问登录页
    await Axios.get('https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index', {
        headers: {
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
        }
    }).then(res => {
        //获取登录页 cookie 和 postKey
        for (let set of res.headers['set-cookie']) {
            cookie += '; ' + set.split(';')[0];
        }
        cookie = cookie.substr(2);
        postKey = /"pixivAccount\.postKey":"([0-9a-z]+)"/.exec(res.data)[1];
    });

    //向登录 API 发送 POST
    await Axios.post('https://accounts.pixiv.net/api/login?lang=zh', Qs.stringify({
        pixiv_id: '邮箱',
        password: '密码',
        captcha: '',
        g_recaptcha_response: '',
        post_key: postKey,
        source: 'pc',
        ref: 'wwwtop_accounts_index',
        return_to: 'https://www.pixiv.net/'
    }), {
        headers: {
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
            "content-type": "application/x-www-form-urlencoded",
            "accept": "application/json",
            "cookie": cookie
        }
    }).then(res => {
        console.log(res.data);
        console.log(res.headers['set-cookie']);
    });
}

test();

返回内容与返回 header 中的 set-cookie:

可喜可贺,也就是说确实是 cookie 的锅

准确地说是因为 sub_filter 只能替换 response 中的内容,我没有想到 set-cookie 的 domain 由于是 .pixiv.net,不会被任何机制自动替换,与反代域名不同,没有 set 上,因此导致发出登录 POST 的时候没有携带上这个 cookie

知道了问题核心所在,那么解决方案自然也就不难想到了,经过搜索我找到了 Nginx 官方文档中有提到 proxy_cookie_domain

Syntax:
proxy_cookie_domain off;
proxy_cookie_domain domain replacement;

Default:
proxy_cookie_domain off;

Context: http, server, location

This directive appeared in version 1.1.15.

Sets a text that should be changed in the domain attribute of the "Set-Cookie" header fields of a proxied server response.

问题解决√

而且没想到解决方式竟如此简单,只不过是加一行配置的事

我的文章对您有帮助吗?
我很可爱 请给我钱
扫一扫拿红包 → 扫商家收款码 → 转账与红包相等的金额
即可免费赞赏,又可拿支付宝奖励金!
现在支付宝超抠门的,红包只给一两分钱了
Last modification:November 10th, 2018 at 02:29 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment

7 comments

  1. fuochai  Windows 10 x64 Edition(Windows 10 x64 Edition) / Google Chrome 70.0.3538.77(Google Chrome 70.0.3538.77)

    对于我这种渣渣,更新=头图推送(确信)

    1. 神代綺凜  Windows 10 x64 Edition(Windows 10 x64 Edition) / Google Chrome 70.0.3538.77(Google Chrome 70.0.3538.77)
      @fuochai

      新鲜的头图推送

  2. Zero  Windows 10 x64 Edition(Windows 10 x64 Edition) / Google Chrome 70.0.3538.77(Google Chrome 70.0.3538.77)

    不明觉厉

    1. 神代綺凜  Windows 10 x64 Edition(Windows 10 x64 Edition) / Google Chrome 70.0.3538.77(Google Chrome 70.0.3538.77)
      @Zero

      想试试我搭好的吗

      1. 星夜的蓝天  Windows 7 x64 Edition(Windows 7 x64 Edition) / Google Chrome 70.0.3538.102(Google Chrome 70.0.3538.102)
        @神代綺凜

        想啊想啊……

        1. 神代綺凜  Windows 10 x64 Edition(Windows 10 x64 Edition) / Google Chrome 70.0.3538.102(Google Chrome 70.0.3538.102)
          @星夜的蓝天

          自行解密(

          cGl4aXYucmlw
      2. Zero  Windows 10 x64 Edition(Windows 10 x64 Edition) / Google Chrome 70.0.3538.77(Google Chrome 70.0.3538.77)