如何隐藏对javascript文件的直接访问?

如何隐藏对javascript文件的直接访问?,javascript,php,Javascript,Php,我正在运行LAMP web服务器 我希望在我的页面中包含脚本文件,包括: <script src="http://domain.com/script.js"></script> 我想参观http://domain.com/script.js 显示错误或空白页 我已经看到了,答案是混淆它,或者混淆的安全性是不好的 这不是为了安全。我想阻止机器人自动提取我的代码。我同意人类用户获得代码。我只是想把它作为混淆的替代方法 我已经尝试使用base64_编码的$\u GET和$\u

我正在运行LAMP web服务器

我希望在我的页面中包含脚本文件,包括:

<script src="http://domain.com/script.js"></script>
我想参观http://domain.com/script.js 显示错误或空白页

我已经看到了,答案是混淆它,或者混淆的安全性是不好的

这不是为了安全。我想阻止机器人自动提取我的代码。我同意人类用户获得代码。我只是想把它作为混淆的替代方法

我已经尝试使用base64_编码的$\u GET和$\u会话参数来实现这一点。我想知道是否有更优雅的解决方案

澄清:

我知道用户仍然可以使用Javascript。我完全可以通过Firebug、Chrome的开发工具等访问代码。我只是希望代码可以通过我的标记访问,并且可以直接访问。这不是为了安全,也不是为了隐藏我的代码

澄清2:

我之所以需要这样做,是因为我们公司最近发现一家竞争对手正在运行脚本从我们的网站上刮取数据。我希望能够防止通过脚本刮取数据,并强制他们手动执行

我的问题非常明确,如何通过我的标记访问文件,并直接访问

我想到两个选择:

防止热链接解决方案 正如@MichaelBerkowski所指出的,这与不允许图像热链接的常见要求非常相似,同样的解决方案也适用,但有同样的注意事项和陷阱。基本上,它是以下任一项或两者的组合:

检查JavaScript文件请求的REFERER sic头,如果REFERER未引用您的某个页面,则拒绝这些请求

记住在短时间内请求HTML页面的机器的IP地址,比如最多一分钟,并且只允许这些IP地址下载JavaScript文件,拒绝来自所有其他IP地址的尝试

第一个是琐碎的,但也被琐碎地忽略了。第二种方法不那么琐碎,但也可以通过简单地发出HTML请求,然后忽略结果而轻易绕过,但至少需要发出请求

在HTML中嵌入JavaScript 另一种方法是使用Apache模块缩小脚本,并在标记所在的位置将其插入HTML文件,从而生成codehere标记。然后就没有JavaScript文件可请求了。这意味着多个页面上的同一个JavaScript不会从缓存中受益,但它的好处是A不需要单独的HTTP请求,B使人们无法下载您的JavaScript文件,因为您根本无法将其托管在外部可见的位置


以上两种情况都不意味着人们无法访问您的代码,因为从根本上说这是不可能的,您所能做的最好的事情就是模糊处理,而反模糊处理程序非常好;从根本上说,如果浏览器可以运行你的脚本,任何人都可以看到它,但是从这个问题的评论中可以清楚地看到你理解。

< P>在你的头脑中有2个解释,你可以考虑使用PHP会话。 您可以首先让用户点击需要验证码才能继续的页面。提交并验证验证验证码后,PHP会话将启动或更新为布尔值$isHuman,表明您确实在与人打交道


脚本请求被定向到一个php页面,该页面仅在会话存在且$isHuman为true时提供脚本

正如一些人在评论中试图解释的那样,这实际上是不可能的,因为服务器无法知道JS文件是作为HTML页面的一部分被请求的还是它自己被请求的

最接近实现这一点的方法是创建一个随机字符串,在生成HTML时将其附加到脚本中,并在调用JS时检查该字符串

顺便说一句,验证码就是这样工作的

在你的HTML中

在你身上

您必须将.js文件更改为.php

<?php
session_start();

//check make sure session variable matches the appended string
if(!isset($_SESSION['JS_STR']) || !isset($_GET['str']) || $_GET['str'] !== $_SESSION['JS_STR']) die("you don't have permission to view this");

//tell the browser your serving some JS
header('Content-Type: application/javascript');

?>
window.alert("your JS goes here...");

我选择只执行访问StackOverflow之前启动的$\u SESSION/$\u GET/$\u后置门控脚本

这个解决方案并不完美,但它适合我的需要,因为脚本可以通过我的标记访问,但不能直接访问。这是我所做工作的简化版本:

文件1是生成用户看到的HTML页面的PHP文件。此文件创建一个随机值,并将该值设置为会话。使用此随机值作为GET参数包括脚本文件2

文件1:

<?php
session_start();
$gate['first_gate'] = crypt((time() * md_rand()) . 'salt');
$gate['second_gate'] = null;
$_SESSION['gate'] = json_encode($gate);
?>
<html>
    ...
    <!--this is just the HTML page including the script-->
    <script src="file_2.php?gate=<?=base64_encode(json_encode($gate))?>"></script>
    ...
</html>
文件2是PHP文件,用作实际JavaScript代码的入口。它验证随机化会话变量是否等于GET参数,然后使用POST请求从文件3获取代码

文件2:

文件3在直接查看页面时不可访问,因为它退出时没有来自文件2的POST数据。机器人程序仍然可以通过POST请求ping它,所以这里应该添加一些额外的安全措施

文件3:

 <?php
 $session_gate = json_decode($_SESSION['gate']);
 $post_gate = json_decode(base64_decode($_POST['gate']));
 //Exit without a POST request. Use a more specific value, other than
 //the $_POST superglobal by itself (just using $_POST for illustrative purposes)
 if(!$_POST) exit; //or print an error message
 //Exit if the session value != the get value
 if($get_gate->second_gate != $session_gate->second_gate) exit;

 //Set both gates to null to prevent re-visit
 $session_gate->first_gate = null;
 $session_gate->second_gate = null;
 $_SESSION['gate'] = json_encode($session_gate);
 //Additional safety measures (such as IP address/HOST check) here, if desired
 header('Content-Type: application/javascript');
 ?>
 //Javascript code here

根据您的回答,这是您的解决方案的简化版本:

<?php
//file1
session_start();
$token = uniqid();
$_SESSION['token'] = $token;
?>
<!--page html here-->
<script src="/js.php?t=<?php echo $token;?>"></script>

注:主要变化如下:

简化代币系统。由于令牌位于页面源代码中,所以它需要做的只是保持其唯一性,并尝试通过编码使其“更安全”,而不采取任何措施

实际的js文件保存在web根目录之外,因此即使您知道文件名,也无法直接访问


请注意,我仍然坚持我对禁止使用IP机器人的评论。此解决方案将使抓取变得更加困难,但并非不可能,并且可能会对真正的访问者造成无法预见的后果。

如果您认为必须这样做,请查看这些有关防止图像文件热链接的问题,并将相同的方法应用于.js。但是,这些方法很容易被伪造referer头打败。我认为您并不真正了解Web服务器如何提供内容的体系结构,但您所要求的并不是真正可能的。无论您做什么,JavaScript仍将对用户可用。@请注意,这是另一个问题—我希望通过标记包含的同一个文件也可以直接访问。刚刚看过澄清2,我想说您最好采用不同的方法。刮刀很容易识别,因为它们很快就能击中所有端点。最好是识别出有问题的IP,或者完全阻止它们,或者如果您觉得有问题,向它们发送旧的/不正确的数据。其他建议要么是无用的,scraper可能会在js之前请求html,就像普通浏览器一样,它甚至可能是自动浏览器,要么会给您的真正用户带来不便,captchas是一种痛苦。@Mark:确实不是。我认为这些评论非常清楚,我认为进一步讨论这个问题对答案没有好处。谢谢你认真回答我的问题。不幸的是,这并不能解决我的问题——我已经有了一个打印$\u服务器超全局的测试脚本。无论是包含的还是直接访问的,这些值都是相同的。@user3191820:$\u服务器超全局与它有什么关系?我说的是REFERER头,和/或内存中的IP地址数据库,和/或将脚本嵌入HTML.@T.J.Crowder$\u服务器超全局包含HTTP\u REFERER和REMOTE\u ADDR IP地址值。在我的本地计算机上,这两种情况下的值都是相同的,分别为“localhost”和“127.0.0.1”。@user3191820:这是您的本地计算机。在真实的生产环境中尝试一下。指向脚本文件的热链接将具有不同的引用器,如果所有层都正确设置,则远程添加器应具有远程IP,而不是本地IP。这更有可能停止数据刮取,而不是您实际要求的。所有这一切意味着机器人必须在请求脚本之前获得第一页。验证码不是这样工作的。CAPTCHA需要人工干预,这里没有。CAPTCHA通过在一个页面上创建会话变量并在下一个页面上验证来工作。除了缺乏人与人之间的互动之外,是的,验证码就是这样工作的。但是是的,正如我和其他人所说,这是浪费时间。是的,但是在这里,你在生成它之后马上给出答案。这就是为什么它是浪费时间的原因。这样做的唯一好处是防止有人查看只有文件名的文件。+1用于回答您自己的问题。但是,这可以简化和改进-参见我的答案
 <?php
 $session_gate = json_decode($_SESSION['gate']);
 $post_gate = json_decode(base64_decode($_POST['gate']));
 //Exit without a POST request. Use a more specific value, other than
 //the $_POST superglobal by itself (just using $_POST for illustrative purposes)
 if(!$_POST) exit; //or print an error message
 //Exit if the session value != the get value
 if($get_gate->second_gate != $session_gate->second_gate) exit;

 //Set both gates to null to prevent re-visit
 $session_gate->first_gate = null;
 $session_gate->second_gate = null;
 $_SESSION['gate'] = json_encode($session_gate);
 //Additional safety measures (such as IP address/HOST check) here, if desired
 header('Content-Type: application/javascript');
 ?>
 //Javascript code here
<?php
//file1
session_start();
$token = uniqid();
$_SESSION['token'] = $token;
?>
<!--page html here-->
<script src="/js.php?t=<?php echo $token;?>"></script>
header('Content-Type: application/javascript');
$token = isset($_GET['t'])? $_GET['t'] : null;
if(!isset($_SESSION['token']) || $_SESSION['token'] != $token){
    //lets mess with them and inject some random js, ih this case a random chunk of compressed jquery
    die('n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)}');
}
//regenerate token, this invalidates current token
$token = uniqid();
$_SESSION['token'] = $token;
?>
$.getScript('js2.php?t=<?php echo $token;?>');
<?php
//js2.php
//much the same as before
session_start();
header('Content-Type: application/javascript');
$token = isset($_GET['t'])? $_GET['t'] : null;
if(!isset($_SESSION['token']) || $_SESSION['token'] != $token){
    //lets mess with them and inject some random js, ih this case a random chunk of compressed jquery
    die('n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)}');
}
unset($_SESSION['token']);
//get actual js file, from a folder outside of webroot, so it is never directly accessable, even if the filename is known
readfile('../js/main.js');