Posts tagged ‘API’

腾讯天气接口

地址:http://mat1.qq.com/weather/inc/minisite2_{$CITY_CODE}.js

资源在这段JS里面,自己整理:

/* http://www.qq.com/js/j.soso.weatherV2.0.2.js */

MiniSite.Weather = {
	defaultCity: 125,
	city: {
		"北京市": {
			"_": 125,
			"北京市": 125
		},
		"上海市": {
			"_": 252,
			"上海市": 252
		},
		"天津市": {
			"_": 127,
			"天津市": 127,
			"塘沽区": 132
		},
		"重庆市" : {
			"_": 212,
			"奉节区": 201,
			"重庆市": 212,
			"涪陵区": 213
		},
		"香港": {
			"_": 1,
			"香港": 1
		},
		"澳门": {
			"_": 2,
			"澳门": 2
		},
		"台湾省": {
			"_": 280,
			"台北": 280
		},
		"安徽省": {
			"_": 248,
			"合肥市": 248,
			"安庆市": 253,
			"蚌埠市": 243,
			"亳州市": 238,
			"巢湖市": 100,
			"池州市": 102,
			"滁州市": 95,
			"阜阳市": 241,
			"淮北市": 77,
			"淮南市": 75,
			"黄山市": 254,
			"六安市": 242,
			"马鞍山市": 76,
			"宿州市": 239,
			"铜陵市": 92,
			"芜湖市": 249,
			"宣城市": 105
		},
		"福建省": {
			"_": 276,
			"福州市": 276,
			"龙岩市": 277,
			"南平市": 274,
			"宁德市": 275,
			"莆田市": 107,
			"泉州市": 279,
			"三明市": 278,
			"厦门市": 287,
			"漳州市": 286,
			"浦城市": 271
		},
		"甘肃省": {
			"_": 57,
			"兰州市": 57,
			"白银市": 58,
			"定西市": 60,
			"甘南州": 225,
			"嘉峪关市": 378,
			"金昌市": 50,
			"酒泉市": 379,
			"临夏市": 229,
			"陇南市": 380,
			"平凉市": 90,
			"庆阳市": 91,
			"天水市": 377,
			"武威市": 51,
			"张掖市": 49
		},
		"广东省": {
			"_": 292,
			"广州市": 292,
			"潮州市": 336,
			"东莞市": 334,
			"佛山市": 331,
			"河源市": 293,
			"江门市": 332,
			"揭阳市": 337,
			"茂名市": 302,
			"梅州市": 285,
			"清远市": 284,
			"汕头市": 294,
			"汕尾市": 297,
			"韶关市": 283,
			"深圳市": 296,
			"阳江市": 301,
			"云浮市": 338,
			"湛江市": 300,
			"肇庆市": 291,
			"中山市": 335,
			"珠海市": 330,
			"南雄市": 235,
			"佛冈市": 322,
			"梅县市": 323,
			"电白市": 324,
			"高要市": 325
		},
		"广西": {
			"_": 295,
			"南宁市": 295,
			"百色市": 288,
			"北海市": 299,
			"崇左市": 343,
			"防城港市": 339,
			"贵港市": 289,
			"桂林市": 232,
			"河池市": 281,
			"贺州市": 341,
			"来宾市": 342,
			"柳州市": 282,
			"钦州市": 298,
			"梧州市": 290,
			"玉林市": 340
		},
		"贵州省": {
			"_": 227,
			"贵阳市": 227,
			"安顺市": 226,
			"毕节地区": 219,
			"六盘水市": 368,
			"黔西南州": 230,
			"铜仁地区": 221,
			"遵义市": 220
		},
		"海南省": {
			"_": 303,
			"海口市": 303,
			"白沙黎族自治县": 354,
			"保亭黎族苗族自治县": 357,
			"澄迈市": 351,
			"儋州市": 347,
			"定安县": 352,
			"东方市": 350,
			"临高市": 353,
			"琼海市": 346,
			"三亚市": 344,
			"屯昌县": 345,
			"万宁市": 349,
			"文昌市": 348,
			"乐东黎族自治县": 355,
			"陵水黎族自治县": 356,
			"琼中黎族苗族自治县": 358
		},
		"河北省": {
			"_": 82,
			"石家庄市": 82,
			"保定市": 130,
			"沧州市": 131,
			"承德市": 121,
			"邯郸市": 3,
			"衡水市": 8,
			"廊坊市": 126,
			"秦皇岛市": 122,
			"唐山市": 128,
			"邢台市": 86,
			"张家口市": 120
		},
		"河南省": {
			"_": 189,
			"郑州市": 189,
			"安阳市": 89,
			"鹤壁市": 260,
			"济源市": 309,
			"焦作市": 251,
			"开封市": 207,
			"洛阳市": 228,
			"漯河市": 307,
			"南阳市": 192,
			"平顶山市": 231,
			"濮阳市": 305,
			"三门峡市": 188,
			"商丘市": 308,
			"新乡市": 304,
			"信阳市": 198,
			"许昌市": 306,
			"周口市": 193,
			"驻马店市": 197
		},
		"黑龙江省": {
			"_": 17,
			"哈尔滨市": 17,
			"大庆市": 40,
			"大兴安岭地区": 5,
			"鹤岗市": 12,
			"黑河市": 6,
			"鸡西市": 18,
			"佳木斯市": 13,
			"牡丹江市": 97,
			"七台河市": 42,
			"齐齐哈尔市": 10,
			"双鸭山市": 15,
			"绥化市": 11,
			"伊春市": 14,
			"漠河市": 38,
			"绥芬河市": 98
		},
		"湖北省":{
			"_": 211,
			"武汉市": 211,
			"鄂州市": 314,
			"恩施州": 208,
			"黄冈市": 203,
			"黄石市": 310,
			"荆门市": 202,
			"潜江市": 320,
			"神农架林区": 321,
			"随州市": 317,
			"天门市": 319,
			"仙桃市": 318,
			"咸宁市": 316,
			"襄樊市": 196,
			"孝感市": 315
		},
		"湖南省": {
			"_": 218,
			"长沙市": 218,
			"常德市": 387,
			"郴州市": 233,
			"衡阳市": 328,
			"怀化市": 217,
			"娄底市": 329,
			"邵阳市": 222,
			"湘潭市": 327,
			"益阳市": 223,
			"岳阳市": 215,
			"张家界市": 214,
			"株洲市": 326,
			"桑植市": 311,
			"沅陵市": 312,
			"南岳市": 313
		},
		"吉林省": {
			"_": 103,
			"长春市": 103,
			"白城市": 37,
			"白山市": 119,
			"吉林市": 104,
			"辽源市": 34,
			"四平市": 385,
			"松原市": 96,
			"通化市": 36,
			"延边州": 110,
			"桦甸市": 109,
			"集安市": 118
		},
		"江苏省": {
			"_": 244,
			"南京市": 244,
			"常州市": 250,
			"淮安市": 240,
			"连云港市": 237,
			"南通市": 247,
			"苏州市": 44,
			"宿迁市": 62,
			"泰州市": 61,
			"无锡市": 43,
			"徐州市": 236,
			"盐城市": 246,
			"扬州市": 245,
			"镇江市": 59,
			"盱眙市": 45,
			"赣榆市": 46,
			"东台市": 47,
			"高邮市": 53
		},
		"江西省": {
			"_": 264,
			"南昌市": 264,
			"抚州市": 273,
			"赣州市": 234,
			"景德镇市": 259,
			"九江市": 258,
			"萍乡市": 153,
			"上饶市": 267,
			"新余市": 154,
			"宜春市": 224,
			"鹰潭市": 265,
			"庐山市": 111,
			"玉山市": 137,
			"贵溪市": 138,
			"广昌市": 145
		},
		"辽宁省": {
			"_": 115,
			"沈阳市": 115,
			"鞍山市": 114,
			"本溪市": 116,
			"朝阳市": 112,
			"大连市": 133,
			"丹东市": 124,
			"抚顺市": 117,
			"阜新市": 108,
			"葫芦岛市": 25,
			"锦州市": 113,
			"辽阳市": 29,
			"盘锦市": 26,
			"铁岭市": 30,
			"营口市": 123,
			"瓦房店市": 129
		},
		"内蒙古": {
			"_": 69,
			"呼和浩特市": 69,
			"巴彦淖尔市": 63,
			"包头市": 64,
			"赤峰市": 106,
			"鄂尔多斯市": 383,
			"呼伦贝尔市": 4,
			"通辽市": 101,
			"乌海市": 382,
			"乌兰察布市": 384,
			"锡林郭勒盟": 16,
			"兴安盟": 7,
			"锡林浩特市": 99
		},
		"宁夏": {
			"_": 78,
			"银川市": 78,
			"固原市": 209,
			"石嘴山市": 54,
			"吴忠市": 83
		},
		"青海": {
			"_": 56,
			"西宁市": 56,
			"果洛州": 158,
			"海北州": 48,
			"海东地区": 210,
			"海南州": 55,
			"海西州": 195,
			"黄南州": 157,
			"玉树州": 155
		},
		"山东省": {
			"_": 140,
			"济南市": 140,
			"滨州市": 135,
			"德州市": 134,
			"东营市": 160,
			"菏泽市": 206,
			"济宁市": 146,
			"莱芜市": 165,
			"聊城市": 139,
			"临沂市": 183,
			"青岛市": 144,
			"日照市": 147,
			"泰安市": 141,
			"威海市": 164,
			"潍坊市": 143,
			"烟台市": 136,
			"枣庄市": 159,
			"淄博市": 142,
			"泰山市": 156
		},
		"山西省": {
			"_": 84,
			"太原市": 84,
			"长治市": 9,
			"大同市": 72,
			"晋城市": 94,
			"晋中市": 22,
			"临汾市": 88,
			"吕梁市": 80,
			"朔州市": 70,
			"忻州市": 81,
			"阳泉市": 85,
			"运城市": 93,
			"五台山市": 381
		},
		"陕西省": {
			"_": 186,
			"西安市": 186,
			"安康市": 194,
			"宝鸡市": 375,
			"汉中市": 190,
			"商洛市": 191,
			"铜川市": 374,
			"渭南市": 187,
			"咸阳市": 376,
			"延安市": 87,
			"榆林市": 79
		},
		"四川省": {
			"_": 166,
			"成都市": 166,
			"阿坝州": 163,
			"巴中市": 199,
			"达州市": 200,
			"德阳市": 361,
			"甘孜州": 162,
			"广安市": 364,
			"广元市": 362,
			"乐山市": 171,
			"凉山州": 367,
			"泸州市": 216,
			"眉山市": 365,
			"绵阳市": 167,
			"内江市": 363,
			"南充市": 205,
			"攀枝花市": 360,
			"遂宁市": 204,
			"雅安市": 168,
			"宜宾市": 172,
			"资阳市": 366,
			"自贡市": 359,
			"峨眉山市": 170
		},
		"西藏": {
			"_": 150,
			"拉萨市": 150,
			"阿里地区": 152,
			"昌都地区": 161,
			"林芝地区": 169,
			"那曲地区": 148,
			"日喀则地区": 149,
			"山南地区": 151
		},
		"新疆": {
			"_": 28,
			"乌鲁木齐市": 28,
			"阿克苏地区": 32,
			"阿拉尔市": 23,
			"博尔塔拉州": 27,
			"昌吉州": 19,
			"哈密地区": 41,
			"和田地区": 39,
			"喀什地区": 35,
			"克拉玛依市": 24,
			"克孜勒苏柯州": 20,
			"石河子市": 33,
			"吐鲁番地区": 31,
			"伊犁州": 21,
			"奇台市": 52
		},
		"云南省": {
			"_": 179,
			"昆明市": 179,
			"保山市": 176,
			"楚雄州": 178,
			"大理州": 177,
			"德宏州": 371,
			"迪庆州": 373,
			"红河州": 185,
			"丽江市": 174,
			"临沧市": 182,
			"怒江州": 372,
			"曲靖市": 175,
			"思茅市": 184,
			"文山州": 369,
			"西双版纳州": 370,
			"玉溪市": 181,
			"昭通市": 173,
			"瑞丽市": 180
		},
		"浙江省": {
			"_": 255,
			"杭州市": 255,
			"湖州市": 65,
			"嘉兴市": 256,
			"金华市": 261,
			"丽水市": 268,
			"宁波市": 263,
			"衢州市": 266,
			"绍兴市": 262,
			"台州市": 269,
			"温州市": 272,
			"舟山市": 74,
			"嵊州市": 66,
			"平湖市": 67,
			"石浦市": 68,
			"宁海市": 71,
			"洞头市": 73,
			"定海市": 257
		}
	},
timelapse:null,defaultUrl:"http://mat1.qq.com/www/images/200801/wealth/",WealtherImg:{"晴":{"day":"sun.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"晴,阳光充足":{"day":"sun.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"晴朗":{"day":"sun.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"炎热":{"day":"sun.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"多云":{"day":"cloud.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"大部多云":{"day":"cloud.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"局部多云":{"day":"cloud.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"时有多云":{"day":"cloud.png","night":"night.png","nm":"4.png","em":"3.png","xm":"2.png","mm":"1.png"},"阴":{"day":"shade.png","night":"night.png"},"冷":{"day":"shade.png","night":"night.png"},"阵雨":{"day":"brain.png"},"雷阵雨":{"day":"lrain.png"},"雷雨":{"day":"lrain.png"},"局部雷雨":{"day":"lrain.png"},"零星雷雨":{"day":"lrain.png"},"局部阵雨":{"day":"lrain.png"},"雷阵雨并伴有冰雹":{"day":"lrain.png"},"冰雹雨":{"day":"lrain.png"},"雨夹雪":{"day":"rs.png"},"雨加雪":{"day":"rs.png"},"雨、雨夹雪":{"day":"rs.png"},"雪、雨夹雪":{"day":"rs.png"},"小雨":{"day":"srain.png"},"冰毛雨":{"day":"srain.png"},"毛毛雨":{"day":"srain.png"},"中雨":{"day":"mrain.png"},"大雨":{"day":"brain.png"},"暴雨":{"day":"drain.png"},"暴风雨":{"day":"drain.png"},"大暴雨":{"day":"drain.png"},"特大暴雨":{"day":"drain.png"},"阵雪":{"day":"bsnow.png"},"小阵雪":{"day":"bsnow.png"},"零星阵雪":{"day":"bsnow.png"},"小雪":{"day":"ssnow.png"},"中雪":{"day":"msnow.png"},"吹雪":{"day":"msnow.png"},"雪":{"day":"msnow.png"},"大雪":{"day":"bsnow.png"},"暴雪":{"day":"bsnow.png"},"暴风雪":{"day":"bsnow.png"},"局部暴雪":{"day":"bsnow.png"},"雾":{"day":"fog.png"},"薄雾":{"day":"fog.png"},"烟雾":{"day":"fog.png"},"冻雨":{"day":"rs.png"},"冰雨":{"day":"rs.png"},"沙尘暴":{"day":"sand.png"},"小到中雨":{"day":"srain.png"},"中到大雨":{"day":"mrain.png"},"大到暴雨":{"day":"brain.png"},"暴雨-大暴雨":{"day":"drain.png"},"大暴雨-特大暴雨":{"day":"drain.png"},"热带风暴":{"day":"drain.png"},"飓风":{"day":"drain.png"},"小到中雪":{"day":"ssnow.png"},"大到暴雪":{"day":"bsnow.png"},"冰雹":{"day":"bsnow.png"},"浮尘":{"day":"sand.png"},"灰尘":{"day":"sand.png"},"扬沙":{"day":"sand.png"},"风":{"day":"sand.png"},"大风":{"day":"sand.png"},"龙卷风":{"day":"sand.png"},"强沙尘暴":{"day":"sand.png"}},lunarInfo:new Array(0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,0x06ca0,0x0b550,0x15355,0x04da0,0x0a5d0,0x14573,0x052d0,0x0a9a8,0x0e950,0x06aa0,0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b5a0,0x195a6,0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,0x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0),lYearDays:function(y)
{var i,sum=348
for(i=0x8000;i>0x8;i>>=1)sum+=(MiniSite.Weather.lunarInfo[y-1900]&i)?1:0
return(sum+MiniSite.Weather.leapDays(y))},leapDays:function(y)
{if(MiniSite.Weather.leapMonth(y))return((MiniSite.Weather.lunarInfo[y-1900]&0x10000)?30:29)
else return(0)},leapMonth:function(y)
{return(MiniSite.Weather.lunarInfo[y-1900]&0xf)},monthDays:function(y,m)
{return((MiniSite.Weather.lunarInfo[y-1900]&(0x10000>>m))?30:29)},Lunar:function(objDate)
{var i,leap=0,temp=0
var baseDate=new Date(1900,0,31)
var offset=(objDate-baseDate)/86400000
this.dayCyl=offset+40
this.monCyl=14;for(i=1900;i<2050&&offset>0;i++)
{temp=MiniSite.Weather.lYearDays(i)
offset-=temp
this.monCyl+=12}
if(offset<0)
{offset+=temp;i--;this.monCyl-=12}
this.year=i
this.yearCyl=i-1864
leap=MiniSite.Weather.leapMonth(i)
this.isLeap=false
for(i=1;i<13&&offset>0;i++)
{if(leap>0&&i==(leap+1)&&this.isLeap==false)
//{--i;this.isLeap=true;temp=leapDays(this.year);}
{--i;this.isLeap=true;temp=MiniSite.Weather.leapDays(this.year);}
else
{temp=MiniSite.Weather.monthDays(this.year,i);}
if(this.isLeap==true&&i==(leap+1)){this.isLeap=false}
offset-=temp
if(this.isLeap==false)this.monCyl++}
if(offset==0&&leap>0&&i==leap+1)
{if(this.isLeap)
{this.isLeap=false;}
else
{this.isLeap=true;--i;--this.monCyl;}}
if(offset<0){offset+=temp;--i;--this.monCyl;}
this.month=i
this.day=offset+1
if(Math.floor(this.day)==MiniSite.Weather.monthDays(this.year,this.month))
{this.day=0;}
return Math.floor(this.day);},getMoon:function(d)
{if(d==0)
{return"night";}
else if(d>0&&d<3)
{return"nm";}
else if((d>2&&d<=6)||23<d)
{return"em";}
else if((d>6&&d<15)||(d>16&&d<24))
{return"xm";}
else if(d==15||d==16)
{return"mm";}},getWealth:function(wealth)
{var ret,T;if(wealth.indexOf("转")>=0)
{var tmp=wealth.split("转");ret=tmp[1];}
else
{ret=wealth;}
var date=new Date();var LunarDay=MiniSite.Weather.Lunar(date);var t=date.getHours();var m=date.getMinutes();if(ret=="晴"||ret=="多云"||ret=="炎热"||ret=="晴,阳光充足"||ret=="晴朗"||ret=="时有多云"||ret=="大部多云"||ret=="局部多云")
{if(t<19)
{if(t>5)
{T="day";}
else
{T=MiniSite.Weather.getMoon(LunarDay);}}
else
{T=MiniSite.Weather.getMoon(LunarDay);}}
else
{T="day";}
if(typeof this.WealtherImg[ret]!=="undefined")
{
	if (typeof MiniSite.Weather.WealtherImg[ret][T] != "undefined")
	{
		return MiniSite.Weather.WealtherImg[ret][T];
	}
	else
	{
		return false;
	}
}
else
{return false;}
return MiniSite.Weather.WealtherImg[ret][T];},_print:function(province,city,conainter)
{if(typeof this.city[province]!="undefined")
{if(typeof this.city[province][city]!="undefined")
{var _city_=this.city[province][city];}
else if(typeof this.city[province]["_"]!="undefined")
{var _city_=this.city[province]["_"];}
else
{var _city_=this.defaultCity;}}
else
{var _city_=this.defaultCity;}
MiniSite.JsLoader.load("http://mat1.qq.com/weather/inc/minisite2_"+_city_+".js?"+Math.round((new Date()).getTime()),function()
{try
{var tmp=__minisite2__weather__.split(" ");var tmp1=tmp[2]+" "+tmp[1];MiniSite.$(conainter).onclick=function()
{window.open("http://www.soso.com/q?cid=w.q.wea&ie=utf-8&w="+encodeURIComponent(tmp[0]+'天气'));call_0410("UNKNOWN","weather",1);}
MiniSite.$(conainter).alt="点击查看"+tmp[0]+"天气详情";MiniSite.$(conainter).title="点击查看"+tmp[0]+"天气详情";if(MiniSite.Weather.getWealth(tmp[3])==false)
{MiniSite.$(conainter).innerHTML="<p><h6><span style='font-size:11px'> "+tmp[1]+"</span></h6><div><a href='http://www.soso.com/q?cid=w.q.wea&ie=utf-8&w="
+encodeURIComponent(tmp[0]+'天气')+"' target='_blank'>"
+tmp[0]+"</a></div></p>";MiniSite.$(conainter).getElementsByTagName("div")[0].style.width="170px";}
else
{if(tmp[0].length>7)
{MiniSite.$(conainter).innerHTML="<span class='img'><img src='"+MiniSite.Weather.defaultUrl+MiniSite.Weather.getWealth(tmp[3])+"'  onload='loadPng(this)' alt='点击查看"+tmp[0]+"天气详情' title='点击查看"+tmp[0]+"天气详情' /></span>"
+"<p><h6>"+tmp[1]+"</h6><div>"
+tmp[0]+"</div></p>";MiniSite.$(conainter).getElementsByTagName("h6")[0].style.paddingTop="8px";MiniSite.$(conainter).getElementsByTagName("a")[1].style.lineHeight="14px";}
else
{MiniSite.$(conainter).innerHTML="<span class='img'><img src='"+MiniSite.Weather.defaultUrl+MiniSite.Weather.getWealth(tmp[3])+"'  onload='loadPng(this)' alt='点击查看"+tmp[0]+"天气详情' title='点击查看"+tmp[0]+"天气详情' /></span>"
+"<p><h6>"+tmp[1]+"</h6><div>"
+tmp[0]+"</div></p>";}}}
catch(e)
{}});},print:function(conainter)
{var ok=function()
{var province=null;var city=null;var ipAddress=MiniSite.Cookie.get("qq_index_ip_1hrcache");if(ipAddress!=null)
{try
{var ipAddressArr=ipAddress.split(",");province=ipAddressArr[0];city=ipAddressArr[1];}
catch(e)
{}}
MiniSite.Weather._print(province,city,conainter);};if(!MiniSite.Cookie.get("qq_index_ip_1hrcache"))
{MiniSite.Weather.timelapse=setTimeout(ok,20000);MiniSite.JsLoader.load("http://fw.qq.com:80/ipaddress",function()
{if(MiniSite.Weather.timelapse!=null)
{clearTimeout(MiniSite.Weather.timelapse);};if(typeof IPData!="undefined")
{MiniSite.Cookie.set('qq_index_ip_1hrcache',IPData[2]+','+IPData[3]);ok();};});}
else
{ok();}}};/*  |xGv00|9e88a0d52b06a0d2668de7195a578a25 */

基于 PHP & MySQL 搭建 OAuth Server

群里的损友们又开始叫了,说在等待我的关于 OAuth 服务器搭建 Demo 的介绍文章。这段时间一直很忙,人一忙,偶尔的一点闲暇就想睡觉,啥也不想做,学习上确实有些懈怠。此处悔过5分钟…….

我之前写过一篇《一步一步搭建 OAuth 认证服务器》的文章,其实也就是介绍了一下 OAuth 的理解和 oauth-php 这个开源的项目,并没有做出一个演示。今天这篇文章就来做一个Demo,我们基于 PHP 来搭建一个 OAuth认证服务器。开始吧!

为了方便理解,可以先看一下在 OAuth 认证过程中的几个关键术语,这也是 RFC5849 中 “1.1.  Terminology” 小节的内容。也可以查看其中文版本

想了一下,没有想到好的应用场景,干脆就使用 RFC5849 中的例子吧。这个例子大概的意思是:

Jane (用户,资源的所有者) 将自己度假的照片 (受保护资源) 上传到了图片分享网站A (服务提供方).

她现在想要在另外一个网站B (Client, 消费方) 在线打印这些照片. 一般情况下, Jane 需要使用自己的用户名和密码登陆网站A.

但是, Jane 并不希望将自己的用户名和密码泄露给网站B. 可是网站B需要访问图片分享网站A的图片并将其打印出来.

首先,我们再虚拟机上面搭建三个虚拟主机。我这里搭建的三个主机是:

# 服务提供方 Service Provider 服务提供服务器, 提供受保护资源
www.service.com

# 服务提供方 Service Provider OAuth认证服务器,进行请求认证
auth.service.com

# 消费方 Consumer 客户应用服务器, 用来发起认证请求
www.demo.com

配合上面介绍的应用场景,www.service.com 相当于网站A,而 www.demo.com 则相当于网站B.

接下来,我们为网站 A 虚拟一个用户 Jane,并将其用户名和密码以及她的照片保存在 MySQL 数据库中。

先创建一个数据库,名曰:photo, 在其中新建一个表user:

CREATE DATABASE `photo`;

CREATE TABLE IF NOT EXISTS `user` (
  `userId` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `userName` varchar(20) NOT NULL COMMENT '用户名',
  `password` char(32) NOT NULL COMMENT '会员密码',
  PRIMARY KEY (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表' AUTO_INCREMENT=1 ;

用户有了,现在给用户创建一个表,用来存储用户照片。新建一个表“image”:

CREATE TABLE IF NOT EXISTS `image` (
  `imageId` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '图片Id',
  `userId` int(11) unsigned NOT NULL COMMENT '用户Id',
  `imagePath` varchar(255) NOT NULL COMMENT '图片路径',
  PRIMARY KEY (`imageId`),
  KEY `userId` (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='图片表' AUTO_INCREMENT=1 ;

数据表有了,现在填充一些数据:

INSERTINTO`photo`.`user` (
 `userId` ,
 `userName` ,
 `password`
)
VALUES (
 '1','jane', MD5('123456')
);

INSERTINTO`photo`.`image` (
 `imageId` ,
 `userId` ,
 `imagePath`
)
VALUES (
 NULL ,'1','path/to/jane/image.jpeg'
);

由于 auth.service.com 认证服务器需要提供应用程序认证服务,所以需要创建一个表存储应用程序信息。实际上,还需要一些其他的相关的数据表。

我们这里使用的是 MySQL 数据库,打开浏览器,访问 http://auth.service.com/oauth-php/library/store/mysql/install.php 来进行数据表的安装。事先需要编辑 install.php 进行数据库配置。安装完毕,请将该文件的数据库连接部分重新注释掉。

下面来实现OAUTH服务器端的应用注册功能。

首先在 oauth.service.com 服务器下新建一个 config.inc.php 文件,文件内容如下:

<?php
// 数据库连接信息
$dbOptions = array(
	'server'   => 'localhost',
	'username' => 'root',
	'password' => '123456',
	'database' => 'photo'
);
?>

该文件的主要作用是保存数据库连接信息。然后继续新建一个 “oauth_register.php” 文件,文件内容如下:

<?php
// 当前登录用户
$user_id = 1;

// 来自用户表单
$consumer = array(
    // 下面两项必填
    'requester_name'         => 'Fising',
    'requester_email'        => 'Fising@qq.com',

    // 以下均为可选
    'callback_uri'           => 'http://www.demo.com/oauth_callback',
    'application_uri'        => 'http://www.demo.com/',
    'application_title'      => 'Online Printer',
    'application_descr'      => 'Online Print Your Photoes',
    'application_notes'      => 'Online Printer',
    'application_type'       => 'website',
    'application_commercial' => 0
);

include_once 'config.inc.php';
include_once 'oauth-php/library/OAuthStore.php';

// 注册消费方
$store = OAuthStore::instance('MySQL', $dbOptions);
$key   = $store->updateConsumer($consumer, $user_id);

// 获取消费方信息
$consumer = $store->getConsumer($key, $user_id);

// 消费方注册后得到的 App Key 和 App Secret
$consumer_id     = $consumer['id'];
$consumer_key    = $consumer['consumer_key'];
$consumer_secret = $consumer['consumer_secret'];

// 输出给消费方
echo 'Your App Key: ' . $consumer_key;
echo '<br />';
echo 'Your App Secret: ' . $consumer_secret;
?>

这时候,通过浏览器访问:http://auth.service.com 就可以自动注册一个应用(其实就是一个消费方Client)。并且将该应用的 App Key 和 App Secret呈现给你。下面 www.demo.com 站点将使用这2个字符串进行认证。所以现在先把这两个值保存起来备用。

看到的页面应该类似于:

Your App Key: de94eb65317c0d7a00af1261fc26882c04df0f850
Your App Secret: 7769ae71e703509a92c7f3816a9268af

这样,消费方注册功能就完成了。

接下来,消费方 www.demo.com 就可以使用这个 App Key 和 App Secret,向认证服务器请求未授权的 Request token 了。这一步需要做两件事情:① 消费方 www.demo.com 向 OAuth Server 也就是 auth.service.com 请求未授权的 Request token;② OAuth Server 处理消费方的请求,生成并将未授权的 Request token 返回给消费方;

先来实现第②件任务。

在认证服务器 auth.service.com 的根目录下新建一个文件”request_token.php”, 文件内容是:

<?php
include_once 'config.inc.php';
include_once 'oauth-php/library/OAuthStore.php';
include_once 'oauth-php/library/OAuthServer.php';

$store = OAuthStore::instance('MySQL', $dbOptions); 

$server = new OAuthServer();
$server->requestToken();
exit();
?>

现在认证服务器已经可以响应消费方请求“未授权的token”了。

这里要特别注意一点,如果测试的时候,消费方和服务提供方在同一台服务器,就像我现在的情况,三个服务器都在同一个虚拟机里,那么要注意,客户端请求的时候,可能发生找不到主机的问题。为啥?因为服务器找不到虚拟的 auth.service.com 认证服务器。怎么解决呢?

Windows下面,我们可以设置 hosts 主机表文件,使某一域名的指向某一固定IP地址。Linux下面也有类似的主机表文件,它的位置是 /etc/hosts,接下来如何做,这里就不用讲啦。

现在来实现第①件任务。

首先在消费方数据库服务器将认证服务器添加到消费方的 OAuthStore 中。这里的数据库安装方式与服务方相同,不再赘述。

然后,我们再消费方服务器 www.demo.com 的根目录添加一个 “config.inc.php” 文件,保存消费方自己的数据库连接信息。我这里由于使用的是虚拟主机,恰好消费方和认证服务器的数据库连接参数是一样的。实际情况可能不是这样。文件的内容与上面的类似:

<?php
// 数据库连接信息
$dbOptions = array(
	'server'   => 'localhost',
	'username' => 'root',
	'password' => '123456',
	'database' => 'photo'
);
?>

然后,在消费方服务器根目录继续添加一个文件,add_server.php, 用来向消费方的数据库中存储认证服务器的信息。其实一般的,这个信息可能会直接写在配置文件里,不过,oauth-php提供了更加强大的数据库的存储方案而已。该文件的内容是:

<?php
include_once 'config.inc.php';
include_once 'oauth-php/library/OAuthStore.php';

$store = OAuthStore::instance('MySQL', $dbOptions);

// 当前用户的ID, 必须为整数
$user_id = 1;

// 服务器描述信息
$server = array(
    'consumer_key'      => 'de94eb65317c0d7a00af1261fc26882c04df0f850',
    'consumer_secret'   => '7769ae71e703509a92c7f3816a9268af',
    'server_uri'        => 'http://auth.service.com/',
    'signature_methods' => array('HMAC-SHA1', 'PLAINTEXT'),
    'request_token_uri' => 'http://auth.service.com/request_token.php',
    'authorize_uri'     => 'http://auth.service.com/authorize.php',
    'access_token_uri'  => 'http://auth.service.com/access_token.php'
);

// 将服务器信息保存在 OAuthStore 中
$consumer_key = $store->updateServer($server, $user_id);
?>

这样,通过浏览器访问一下该文件,http://www.demo.com/add_server.php, 服务器的相关信息就会被保存起来了。用于生产环节时,这里可能是一个简单的管理系统,可以用来管理认证服务器列表。注意,上面文件里的 key 和 secret 正是我们之前在认证服务器 http://auth.service.com 注册消费方应用时得到的。

有了认证服务器的相关信息,我们现在可以去获取“未认证的token”了。在 www.demo.com 根目录新建一个文件 index.php:

<?php
if(isset($_GET['req']) && ($_GET['req'] == 1)){
	include_once 'config.inc.php';
	include_once 'oauth-php/library/OAuthStore.php';
	include_once 'oauth-php/library/OAuthRequester.php';

	$store = OAuthStore::instance('MySQL', $dbOptions);

	// 用户Id, 必须为整型
	$user_id = 1;

	// 消费者key
	$consumer_key = '286cec927c4c5482e75d80759e9fdd8904df10e2f';

	// 从服务器获取未授权的token
	$token = OAuthRequester::requestRequestToken($consumer_key, $user_id);
	var_dump($token);
	die();
}
else{
?>
	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
	<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>测试页面</title>
	</head>

	<body>
	<p>消费放测试页面,点击下面的按钮开始测试</p>
	<input type="button" name="button" value="Click Me" id="RequestBtn"/>
	<script type="text/javascript">
	document.getElementById('RequestBtn').onclick = function(){
		window.location = 'index.php?req=1';
	}
	</script>
	</body>
	</html>
<?php
}
?>

现在,通过浏览器访问 www.demo.com/index.php页面,然后点击页面上的“Click Me”按钮,开始向auth.service.com服务器请求“未授权的token”。如果最后结果显示类似于:

array(2) { ["authorize_uri"]=> string(37) "http://auth.service.com/authorize.php" ["token"]=> string(41) "dc8e8df797d9737b0acfe7a8b549005604df5e485" }

那么恭喜你,获取“未授权的token”这一步,已经顺利完成了。

接下来,根据 OAuth 验证的流程,应该是重定向用户浏览器到 auth.service.com 进行 token 授权。

在 auth.demo.com 服务器根目录新建一个文件 authorize.php, 代码如下:

<?php
session_start();

if (empty($_SESSION['authorized']))
{
	$uri = $_SERVER['REQUEST_URI'];
	header('Location: /login.php?goto=' . urlencode($uri));
	exit();
}

include_once 'config.inc.php';
include_once 'oauth-php/library/OAuthStore.php';
include_once 'oauth-php/library/OAuthServer.php';

//登陆用户
$user_id = 1;

// 取得 oauth store 和 oauth server 对象
$store = OAuthStore::instance('MySQL', $dbOptions);
$server = new OAuthServer();

try
{
    // 检查当前请求中是否包含一个合法的请求token
    // 返回一个数组, 包含consumer key, consumer secret, token, token secret 和 token type.
    $rs = $server->authorizeVerify();

    if ($_SERVER['REQUEST_METHOD'] == 'POST')
    {
        // 判断用户是否点击了 "allow" 按钮(或者你可以自定义为其他标识)
        $authorized = array_key_exists('allow', $_POST);

        // 设置token的认证状态(已经被认证或者尚未认证)
        // 如果存在 oauth_callback 参数, 重定向到客户(消费方)地址
        $server->authorizeFinish($authorized, $user_id);

        // 如果没有 oauth_callback 参数, 显示认证结果
        // ** 你的代码 **
    }
	else
	{
		echo 'Error';
	}
}
catch (OAuthException $e)
{
    // 请求中没有包含token, 显示一个使用户可以输入token以进行验证的页面
    // ** 你的代码 **
}
?>

如果用户未登录,则要求先行登陆才能进行授权操作。

一步一步搭建 OAuth 认证服务器

现在越来越多开放的互联网公司提供对外的 API 接口,使得第三方应用开发人员可以开发基于该平台接口的应用程序。国外有TwitterFlicker Service等;国内的,像腾讯微博开放平台新浪微博开放平台等等。

这些平台接口的认证方式,无一例外的,都采取了 OAuth 来实现(Twitter原来使用的是Basic Auth方式,后来全面转向OAuth)。

那么,OAuth 是什么?OAuth认证又有什么好处呢?

OAuth 是什么

关于OAuth的定义,在 OAuth官网 的首页上,有一行大大的文字说明:

An open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.

(OAuth) 是一个开放协议,它以一种简单、标准的方式实现对桌面和 Web 的应用程序的安全 API 认证。

OAuth 1.0 协议(中文版 | 英文版)这样介绍OAuth:“OAuth 协议致力于使网站和应用程序(统称为消费方)能够在无须用户透露其认证证书的情况下,通过 API 访问某个web服务(统称为服务提供方)的受保护资源。更一般地说,OAuth 为 API 认证提供了一个可自由实现且通用的方法”。

关于 OAuth 的用途,OAuth 1.0 协议(中文版 | 英文版)上举了一个例子:某打印服务提供商 printer.example.com(消费方),希望在无须用户提供其照片存储站点密码的情况下,访问用户储存在 photos.example.net(服务提供方)上的个人照片。

假如没有 OAuth,用户必须要向消费方也就是 printer 提供自己在服务提供商 photos 上的授权资料(通常是密码),消费方利用这个授权资料,通过服务提供方的权限验证,从而获得要打印的图片。这样看似没有什么问题。但是用户的授权资料,通常在这一过程中被消费方有意或者无意地窃取或泄露,从而对用户和服务提供方的信息安全造成威胁。

而如果利用 OAuth 进行此过程的授权,用户的授权资料并不会传递给第三方(也就是消费方,通常是App应用),而消费方只需要将用户引导至服务提供方的授权页面进行授权,使得消费方获得访问受限资源的权限即可。而在此过程中,用户授权过程是在服务提供方进行的,消费方并不会直接接触到用户的授权资料,因此一般不会造成用户授权资料的泄密,从而既保证了用户和服务提供方的信息安全,又使得消费方完成了对受限资源的读取。可谓一举三得。

个人对 OAuth 授权过程的理解:服务提供方 SP 好比一个封闭院子,只有持卡人才能进入,用户 U 就是持卡人之一。而消费方 C 没有持卡,通常情况下是不能进入的。但是有一天,由于特殊原因,U 需要 C 帮忙去 SP 那里取一样东西。这个时候问题就来了:  C 没有持卡,不能进去院子,而 U 又不能把卡直接给 C (卡上面有很多个人机密信息,不方便外泄哦)。怎么办呢?

哦,对了,U 可以带着 C 去门口,告诉SP:这个人是我认识的,他需要进去帮我拿我的一样东西,请予放行。这样,U 既不用将带有个人私密信息的门卡交给 C,C 也通过验证拿到了属于 U 的东西。

有的人要问了,是不是下次 C 想要再进 SP 的拿 U 的东西的话,是不是就不用 U 的指引了呢?人类社会的情况通常是这样的。可惜,在 HTTP 的世界里,由于 HTTP 是无状态的协议,因此,SP 仍然不会认识 C。所以,每次 C 想要取东西,总是需要 U 的指引。是不是很麻烦呢?呵呵。但是为了安全,麻烦一点又有什何妨!

上面介绍了 OAuth 认证的基本思路,如果你还不理解,可以参考 OAuth认证流程图, 或者查看腾讯微博关于OAuth认证的介绍。OAuth 官方网站就有一篇文档教程《OAuth入门指南》,不过没有中文版本。有兴趣同学的也可以自己看看。

OAuth 认证授权有以下几个特点:

  • 1. 简单:不管是 OAuth 服务提供者还是应用开发者,都很容易于理解与使用;
  • 2. 安全:没有涉及到用户密钥等信息,更安全更灵活;
  • 3. 开放:任何服务提供商都可以实现 OAuth,任何软件开发商都可以使用 OAuth;

那么下面我们就作为服务提供商角色,来实现 OAuth 认证服务器的安装和搭建。

其实,很多先行者已经开发出了很多的 OAuth 消费方代码(客户端)和服务提供方代码(服务端),这里是它们的一些列表,其中包含了 .NET (C#/VB.NET), ColdFusion, Java, Javascript, Jifty, Objective-C, OCaml, Perl, PHP, Python, Ruby, Erlang 和其他语言的一些实现。

通过 Google,我找到了一个开源的 OAuth 服务端代码和消费方开源代码库项目——oauth-php. 我们就借此来实现。关于OAuth,项目主页的介绍是: OAuth Consumer And Server Library For PHP. 它包含一个完整实现的可扩展的OAuth存储,支持 MySQL/MySQLi,  Postgresql,  PDO 和 Oracle 等多种存储方式。

它实现了以下方法:

  • 认证进来的请求
  • 为出去的请求签名
  • 使用 body 为请求签名
  • 为多用户管理消费方的 key 和 token(服务端和消费端)
  • 记录经过类库处理的进出的请求(可以在数据库中进行可选配置)

很多网站都在使用oauth-php, 包括荷兰阿姆斯特丹Mediamatic Lab实验室出品的anyMeta CMS. 目前,oauth-php 的代码主要由Corollarium Technologies 负责维护。

为你的服务器增加 OAuth 非常简单。你需要检查进来的请求中 OAuth 认证细节。首先,我们需要四个控制器 controller 文件:

  • oauth_register.php 使消费方用户获得 key 和密钥
  • request_token.php 返回一个未认证的 request token
  • authorize.php 认证一个request token
  • access_token.php 将认证后的 request token 置换为 access token

以下的例子,假设使用的数据存储器是MySQL。你也可以使用其他数据库——当你第一次使用 OAuthStore 实例的时候指定一个参数,告诉它你要使用的数据库。

$store = OAuthStore::instance('mystore');

这就是假设在存储器目录,你有一个名为 OAuthStoremystore.php 文件。在这里我们使用 OAuthStoreMySQL.php 文件来实现。这也就是说,我们在实例化 OAuthStore 的时候,需要这样做:

$store = OAuthStore::instance('MySQL');

然后,在每个请求被处理之前,都可以检查其是否带有 OAuth 认证信息:

if (OAuthRequestVerifier::requestIsSigned())
{
        try
        {
                $req = new OAuthRequestVerifier();
                $user_id = $req->verify();

                // 如果存在 user_id, 那么作为那个用户角色登录(对于本次请求)
                if ($user_id)
                {
                        // **** 在这里新增你的代码 ****
                }
        }
        catch (OAuthException $e)
        {
                // 请求已经签名,但是认证失败
                header('HTTP/1.1 401 Unauthorized');
                header('WWW-Authenticate: OAuth realm=""');
                header('Content-Type: text/plain; charset=utf8');

                echo $e->getMessage();
                exit();
        }
}

每个消费方都使用 key 和密钥的组合和 token 和 token 密钥的组合来为它的请求进行签名。而在此之前,消费方必须要先获取属于它的消费方 key 和消费方密钥。 oauth_register.php 就是负责分发消费方 key 和密钥的控制器文件。

// 当前登录用户
$user_id = 1;

// 下面的内容应该来自用户填写的表单
$consumer = array(
    // 下面两个是必须的
    'requester_name' => 'John Doe',
    'requester_email' => 'john@example.com',

    // 下面的是可选的
    'callback_uri' => 'http://www.myconsumersite.com/oauth_callback',
    'application_uri' => 'http://www.myconsumersite.com/',
    'application_title' => 'John Doe\'s consumer site',
    'application_descr' => 'Make nice graphs of all your data',
    'application_notes' => 'Bladibla',
    'application_type' => 'website',
    'application_commercial' => 0
);

// 注册消费方
$store = OAuthStore::instance();
$key   = $store->updateConsumer($consumer, $user_id);

// 从数据存储器获得完整的消费方信息
$consumer = $store->getConsumer($key);

// 消费方用户将需要 key 和 secret
$consumer_id = $consumer['id'];
$consumer_key = $consumer['consumer_key'];
$consumer_secret = $consumer['consumer_secret'];

如果你想要更新之前注册的消费方身份,提供消费方id,key 和 secret 。key 和 secret 在更新操作完成之前不会进行改变。

你还可以请求一个特定用户注册的所有消费方列表:

// 当前登录用户
$user_id = 1;

// 取得这个用户注册的全部消费方列表
$store = OAuthStore::instance();
$list = $store->listConsumers($user_id);

request_token.php 这个控制器文件负责返回请求 token(未认证的 request token)。当消费方获取了 key 和 secret 之后,它就可以向服务器端请求未认证的 request token 了:

$server = new OAuthServer();
$token = $server->requestToken();
exit();

authorize.php 控制器文件负责认证一个用户请求 token。这个控制器负责询问用户是否允许消费方访问他的账户。如果允许,那么消费方将可以使用 request token 换取 access token。必须要保证用户在访问下面的代码之前是登录状态。OAuthServer 服务器使用 SESSION 存储一些 OAuth 状态信息,所以必须要开启seesion会话(要么是 session_start 函数,要么就是自动开启)。

// 当前登录用户
$user_id = 1;

// 取得OAuth存储器和OAtuh服务器对象
$store  = OAuthStore::instance();
$server = new OAuthServer();

try
{
    // 检查当前请求中是否包含合法的request token
    // 返回一个包含消费方key, 消费方secret, token, token secret 和 token 类型的数组.
    $rs = $server->authorizeVerify();

    if ($_SERVER['REQUEST_METHOD'] == 'POST')
    {
        // 检查用户是否点击了 'allow' 按钮或者其他你指定的按钮)
        $authorized = array_key_exists('allow', $_POST);

        // 设置 request token 的认证状态(已认证或者是未认证)
        // 当包含 oauth_callback 回调的时候,这些将传给消费方
        $server->authorizeFinish($authorized, $user_id);

        // 没有 oauth_callback 回调, 显示认证结果

        // ** 你的代码 **
   }
}
catch (OAuthException $e)
{
    // 没有需要认证的 request token, 显示一个可以输入 request token 的页面
    // ** 你的代码 **
}

access_token.php 控制器文件负责将认证的request token换成access token。access token 可以被用来为请求签名。

$server = new OAuthServer();
$server->accessToken();

看完本文,如果还是一头雾水,不知道如何下手,可以继续阅读《基于PHP & MySQL 搭建 OAuth Server》。

OAuth Core 1.0 简体中文版