用例:
在开发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传值的方法保留这个信息。