3
3

More than 3 years have passed since last update.

コロナウイルス対応のため、学習コンテンツの無料開放にひと肌脱いだ話。

Last updated at Posted at 2020-03-01

0.まえおき

新型コロナウイルスによる、学校の休校を受け、
教育業界において、コンテンツの無料開放などが流行っています。

そんなさなか、とある老舗の参考書出版会社さまから、
コンテンツ無料解放をしたいと相談を受けたのが金曜日の夜。。。

すばらしい社会貢献!
ひと肌脱ぐことになり、私の休日(ほぼ徹夜)は消え去りました。
(えぇ、もちろん私も無償奉仕です。)

CORS

1.要件/状況

・PDFの書籍データがある
・誰にでも見せたいけど、ダウンロードとか印刷をされてしまっては困る。
 (過度には誰にも見せたくない、、、)
・サーバとか何もない

という、本当にゼロからのスタートです;;






2.実現方法概要

1.PDF(URL)の保護
 PDF自体のDRMとかもあるのかもしれないですが、Adobeが絡むはずなので、(あまり詳しくないです)
 AWSの署名付きURLで期限付きのURLを発行して、URLの流出やクローラに対応。

2.PDFのDLや印刷を禁止する
 ブラウザでのPDF閲覧ではなく、独自Viewer(PDF.js)を採用し、DF閲覧時の機能を制限する。

3.時間もないためにサーバレスで実装
 静的コンテンツであれば、関連するシステム上に配置可能とのことで、
 それ以外については、AWSを多用しまくります。

4.致し方ないこと
 ・それなりにITの知識がある人にはどうしてもPDFデータはDLされちゃう。
 ・画面キャプチャなど、OSによる機能は防げない。






3.という事で、全体のシーケンス

全体概要

もっと、いい案がないのか、、、と思いつつも時間がないので、即実装です。
 

①AWS API Gateway
 ・GETリクエストで、閲覧したい書籍のパラメータを受ける。
 ・APIについては、IP制限が使えないため(IP固定なのか不明・・・)、アクセス元ドメインでのリファラー制限を実施。
   (リファラー詐称をされない限り、APIは実行不可)

③AWS Lamda
 ・受け取った書籍情報について、S3上のPDFに対し署名付きURLを発行する。
 ・そのままURLを返却してしまうと通常のブラウザで見えてしまうので、
  署名付きURLを付与したViewerのURLへリダイレクトさせる。

③PDF.js
 ・パラメータにある署名付きURLのデータをPDF.jsが読み込む。
  (この際、PDF.jsのパラメータを見て、ヨシヨシ!という感の鋭い人には、PDFそのもののアクセスができちゃう。
   ただ、URLがすごく長いのでぱっと見わからないかも。)

④公開領域フォルダ
 ・DLや印刷機能を禁止したカスタマイズ版のPDF.jsを配置します。

⑤PDFの書籍データ
 ・署名付きURLでしかアクセスできないようにします。
 ・Viewerからアクセスされるので特定ドメインのCORS設定も行います。






4.各々の詳細

①②AWSのLambdaコードです。(node.js v12.Xです)

ポイントは、Lambda プロキシ統合利用でのCORS設定と、
302リダイレクトのあたりでしょうか。

APIGatewayとLambdaについては、前回の記事も参考ください。
AWS APIGateway + Lambda プロキシ統合 の利用でCORS設定にはまった話

署名付きURLの発行と302リダイレクト
const https = require('https');

const AWS_ACCESSKEY_ID =  process.env.AWS_ACCESSKEY_ID;
const AWS_SECRET_ACCESSKEY =  process.env.AWS_SECRET_ACCESSKEY;
const PDF_BASE_URL = process.env.PDF_BASE_URL;
const PDF_BUCKET = process.env.PDF_BUCKET;
const PDF_EXPIRES = parseInt(process.env.PDF_EXPIRES,10);

let AWS = require('aws-sdk');
AWS.config.update({accessKeyId: AWS_ACCESSKEY_ID, secretAccessKey: AWS_SECRET_ACCESSKEY,region:"ap-northeast-1"});
let s3 = new AWS.S3({signatureVersion:'v4'});


exports.handler = async function (event, context, callback) {

    const done = (err, res) => callback(null, {
        statusCode: err ? '400' : '302',
        body: res,
        headers: {
            "Access-Control-Allow-Origin": "*", //Lambdaプロキシ統合の場合は自前でCORS設定。
            "Location": res //302リダイレクトのために独自設定
        }
    });

    switch (event.httpMethod) {
        case 'GET':

            const book = event['queryStringParameters']['book'];
            const page = event['queryStringParameters']['page'];

            let presignedURL = await createPresignedURL(PDF_BUCKET,book+"/"+page+".pdf");
            let return_url = PDF_BASE_URL+"?file="+encodeURIComponent(presignedURL);
            console.log('createPresignedURL :' + return_url);  

            done(null,return_url);

            break;
        default:
            done(new Error(`Unsupported method "${event.httpMethod}"`));
    }

};


/**
 * 対象のURLに対して署名を行い、公開用のURLを発行します。
 */
async function createPresignedURL(inBucket,pdffilename){

    const s3params = {
        Bucket: inBucket,
        Key: pdffilename,
        Expires: PDF_EXPIRES
    };
    console.log('createPresignedURL:', JSON.stringify(s3params));

    try {
        let signed_url = await s3.getSignedUrl('getObject', s3params);
        return signed_url;
    } catch (err) {
        console.log("Err:"+err);
        return err;
    }

}

また、API GateWay側にはリダイレクト設定が必要です。
APIGW

あとは、APIGateWayのリソースポリシー設定を行います。
APIGateway全般なんですが、設定などを変えた後、「APIのデプロイ」を忘れがちです。。。
APIGW2

リソースポリシー
{
    "Version": "2012-10-17",
    "Id": "policy example",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:Hogehoge/*",
            "Condition": {
                "StringLike": {
                    "aws:Referer": [
                        "https://foobar/*"
                    ]
                }
            }
        }
    ]
}




③PDF.js(pdfjs-2.2.228)の修正

・PDF.jsについては、DLボタンの禁止
・印刷の禁止
印刷禁止

・縦スクロールを見やすい横スクロールに
・HOSTED_VIEWER_ORIGINSの設定
などを実施しています。

viewer.css
 /* ツールバーのボタン類を削除 */
#sidebarToggle,
#openFile, 
#viewBookmark,
#print,
#download,
#secondaryOpenFile, 
#secondaryViewBookmark,
#secondaryDownload,
#secondaryPrint,
#secondaryToolbarToggle { 
    display: none !important;
}

 /* ブラウザ印刷時のデータ削除 */
@media print{
  body{
    display: none;
  }
}
viewer.js(13900行目あたり)
function getDefaultPreferences() {
  if (!defaultPreferences) {
    defaultPreferences = Promise.resolve({
      "cursorToolOnLoad": 0,
      "defaultZoomValue": "",
      "disablePageLabels": false,
      "enablePrintAutoRotate": false,
      "enableWebGL": false,
      "eventBusDispatchToDOM": false,
      "externalLinkTarget": 0,
      "historyUpdateUrl": false,
      "pdfBugEnabled": false,
      "renderer": "canvas",
      "renderInteractiveForms": false,
      "sidebarViewOnLoad": -1,
      "scrollModeOnLoad": 1, //ここのデフォルトを-1⇒1に変更
      "spreadModeOnLoad": -1,
      "textLayerMode": 1,
      "useOnlyCssZoom": false,
      "viewOnLoad": 0,
      "disableAutoFetch": false,
      "disableFontFace": false,
      "disableRange": false,
      "disableStream": false
    });
  }




④公開領域バケットの設定

(割愛)
公開領域といえども、無駄なアクセスを避けるため、
気持ち程度にリファラー設定。

APIGW

バケットポリシー
{
    "Version": "2012-10-17",
    "Id": "policyexample",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::hogehoge/*",
            "Condition": {
                "StringLike": {
                    "aws:Referer": [
                        "https://foo.bar/*"
                    ]
                }
            }
        }
    ]
}




⑤非公開領域バケットの設定

(割愛)
Viewerから呼び出されるために、CORS設定が必要になります。
CORS

CORSの設定
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>https://hogehohogehoge</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <ExposeHeader>ETag</ExposeHeader>
    <ExposeHeader>Accept-Ranges</ExposeHeader>
    <ExposeHeader>Content-Encoding</ExposeHeader>
    <ExposeHeader>Content-Length</ExposeHeader>
    <ExposeHeader>Content-Range</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>






5.あとがき

サービスサイトから、GETパラメータを渡すことで、無事にPDFの表示ができています。
不用意にコピーされたくない、PDFをWeb上で公開する方法の1案ではないでしょうか。

作成しましたサイトですが、
公開可能でしたら、後日ご紹介したいと思います。
(フロント側については、もう一人のエンジニアの方と実装&ブラッシュアップ中です)

なんだかんだ、時間を要したのは、PDFの加工(原本はGB単位)だったりしました。。。
(PDFを命名ルールを付けながらリネーム、分割、圧縮、結合、ディレクトリへの配置。)

 

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3