用例:

在开发facebook应用的过程当中,经常要判断facebook用户对当前应用的“Like”状态,从而显示不同的内容。

一个典型的例子是在表单上放上一个遮罩层(通常是一个id为shade的div),用户无法透过遮罩层点击输入框,从而诱导用户点击facebook菜单上的 “Like”按钮,页面进行一次跳转,在跳转的过程当中,我们可以从facebook返回的信息当中获得“Like”的状态,进而判断是否取消显示遮罩层。

总体思想:

在嵌入facebook的应用前端页面,加入以下代码(加在body的closing tag之前):



<div id="fb-root"></div>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript">    
FB.init({        
appId: [facebook_app_id], // 注意替换相应的facebook app id        
status: true,        
cookie: true,        
xfbml: true,       
oauth: true   
});   
 FB.Canvas.setAutoResize(10); // 这句调用facebook api来调整嵌入的窗口大小,不是必要的但一般最好加上
</script>



之后,每当刷新facebook应用页面的时候,facebook都会在request里加上一个叫做“signed_request”的parameter,它是由一个“点”符号连接起来的两个乱码字符串,看起来像这样(注意“点”前后没有空格,这边加上空格只是方便阅读):



vlXgu64BQGFSQrY0ZcJBZASMvYvTHu9GQ0YM9rjPSso .  eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsIjAiOiJwYXlsb2FkIn0



前半部分是一个HMAC SHA-256加密的字符串, 后半部分是一个base64url编码的JSON object.

后台实现:

通过将后半部分通过密钥(facebook application secret)加密,将得出的字符串与前半部分进行比较,如果相同则后半部分所包含的JSON object是有效的,进而将后半部分进行base64url解码,得出真正的JSON object。JSON object中一定有一个名为“like”的键,如果其值不为空的话,说明用户已经点击过“Like”按钮了。注意:键值在不同语言中不同,“liked”(c#)或 “1”(php),具体代码示例如下:

PHP:



<?php
$application_secret = [facebook_application_secret]; // 注意替换相应的facebook application secret 
function parse_signed_request($signed_request, $secret) {   
list($encoded_sig, $payload) = explode('.', $signed_request, 2);  

// decode the data  
$sig = base64_url_decode($encoded_sig);  
$data = json_decode(base64_url_decode($payload), true);   

if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {    
error_log('Unknown algorithm. Expected HMAC-SHA256');    
return null;  
}   

// check sig  
$expected_sig = hash_hmac('sha256', $payload, $secret, $raw = true);  
if ($sig !== $expected_sig) {    
error_log('Bad Signed JSON signature!');    
return null;  
}  
return $data;
} 

function base64_url_decode($input) {  
return base64_decode(strtr($input, '-_', '+/'));
}
?> 

$payloadArray = parse_signed_request($_REQUEST["signed_request"], $application_secret);   
<div id="shade" style="<?php
if($payloadArray["page"]["liked"] == "1") echo "display:none;"; // 取消遮罩层
else echo "display:block;"; // 显示遮罩层
?>">      <div id="like-enter"><span>Like us to enter the competition</span></div>   
</div>



C#:



private static string APPLICATION_SECRET = [facebook_application_secret]; // 注意替换相应的facebook application secret
 
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Params["signed_request"] != null)
    {
string decodedPayloadString = ValidateSignedRequest(Request.Params["signed_request"]);
string[] payloadArray = decodedPayloadString.Split(',');
string liked = payloadArray[3];
string likeValue = liked.Substring(7, 5);

if (likeValue == ":true")
        {
            div_shade.Visible = false; // 取消遮罩层
        }
else
        {
            div_shade.Visible = true; // 显示遮罩层
        }

    }
}

private string ValidateSignedRequest( string signedRequest ) {
string[] signedRequestArray = signedRequest.Split( '.' );
string expectedSignature = signedRequestArray[0];
string payload = signedRequestArray[signedRequestArray.Length - 1];

byte[] Hmac = SignWithHmac( UTF8Encoding.UTF8.GetBytes( payload ), UTF8Encoding.UTF8.GetBytes( LikeDetection.APPLICATION_SECRET ) );
string HmacBase64 = ToUrlBase64String( Hmac );

bool result = (HmacBase64 == expectedSignature);
if (result)
    {
string decodedPayload = System.Text.UTF8Encoding.UTF8.GetString( FromBase64ForUrlString( payload ) );
    }

return decodedPayload;
}

private byte[] SignWithHmac( byte[] dataToSign, byte[] keyBody ) {
using ( System.Security.Cryptography.HMACSHA256 hmacAlgorithm = new System.Security.Cryptography.HMACSHA256( keyBody ) ) {
        hmacAlgorithm.ComputeHash( dataToSign );
return hmacAlgorithm.Hash;
    }
}

private string ToUrlBase64String( byte[] Input ) {
return Convert.ToBase64String( Input ).Replace( "=", String.Empty ).Replace( '+', '-' ).Replace( '/', '_' );
}

private byte[] FromBase64ForUrlString( string base64ForUrlInput ) {
int padChars = ( base64ForUrlInput.Length % 4 ) == 0 ? 0 : ( 4 - ( base64ForUrlInput.Length % 4 ) );

    StringBuilder result = new StringBuilder( base64ForUrlInput, base64ForUrlInput.Length + padChars );
    result.Append( String.Empty.PadRight( padChars, '=' ) ).Replace( '-', '+' ).Replace( '_', '/' );

return Convert.FromBase64String( result.ToString() );
}



还值得注意的很重要一点是:通过以上方法获取的“Like”的值,只有在加载facebook应用的home page的时候,也就是说,只有在facebook outer frame刷新的时候才能获取到,如果在facebook内嵌的我们自行搭建的iframe内进行跳转,这个值是无法被再次获取得到的,因此如果想要之后 得到“Like”的状态,就必须用到Cookie或Session或通过URL传值的方法保留这个信息。