##0.まえおき
新型コロナウイルスによる、学校の休校を受け、
教育業界において、コンテンツの無料開放などが流行っています。
そんなさなか、とある老舗の参考書出版会社さまから、
コンテンツ無料解放をしたいと相談を受けたのが金曜日の夜。。。
すばらしい社会貢献!
ひと肌脱ぐことになり、私の休日(ほぼ徹夜)は消え去りました。
(えぇ、もちろん私も無償奉仕です。)
##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による機能は防げない。
もっと、いい案がないのか、、、と思いつつも時間がないので、即実装です。
①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設定にはまった話
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側にはリダイレクト設定が必要です。
あとは、APIGateWayのリソースポリシー設定を行います。
APIGateway全般なんですが、設定などを変えた後、**「APIのデプロイ」**を忘れがちです。。。
{
"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の設定
などを実施しています。
/* ツールバーのボタン類を削除 */
#sidebarToggle,
#openFile,
#viewBookmark,
#print,
#download,
#secondaryOpenFile,
#secondaryViewBookmark,
#secondaryDownload,
#secondaryPrint,
#secondaryToolbarToggle {
display: none !important;
}
/* ブラウザ印刷時のデータ削除 */
@media print{
body{
display: none;
}
}
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
});
}
###④公開領域バケットの設定
(割愛)
公開領域といえども、無駄なアクセスを避けるため、
気持ち程度にリファラー設定。
{
"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設定が必要になります。
<?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を命名ルールを付けながらリネーム、分割、圧縮、結合、ディレクトリへの配置。)