如下,我在Orchard Core框架中添加了一个API的模块,并且定义了对应的权限才可以调用,那么我们现在考虑的就是要怎么去调用它。
首先,我们用Fiddler查看下我们正常的登录的http报文,直接在浏览器输入路径例如: http://192.168.0.225:8082/admin ,会直接跳转到登录页,路径是:http://192.168.0.225:8082/Login?ReturnUrl=%2Fadmin
admin被添加到一个ReturnUrl的参数后面,输入账号密码然后回车登录到后台。这样一个登录操作就完成了,我们再看看Fiddler的报文:
在login的时候发送了一个__RequestVerificationToken参数,并且在返回的时候set了一个cookie,这个cookie包含了这个登录用户的身份信息等等,
所以当我们关闭浏览器但没有logout退出,再次进入admin后台时,不需要再次登录操作,就是因为在这个时候记录了cookie。
那么__RequestVerificationToken参数又是怎么来的呢?我们知道这整个过程,每一个http报文都是紧接着上一条报文的结果,
我们看看上一个报文,它返回了一个Document,我们在这个Document找到了这个元素 name="__RequestVerificationToken"
这个元素的value就是__RequestVerificationToken参数的值。
再看看这个Document,里面有一个Form,意思就是Form里的内容会submit到Form对应的action中。
我一开始以为,了解了这个过程,我们就可以用ajax模拟登录得到cookie了,但是用如下代码,返回的总是400错误,浏览器拒绝了请求。
$.ajax({ url: 'http://192.168.0.225:8082/admin', type: 'get', cache: false, success: function (data) { var objval = $(data).find("[name='__RequestVerificationToken']").val(); var url = $(data).find(".form-horizontal").attr("action"); $.ajax({ url: "http://192.168.0.225:8082" +url, type: 'post', cache: false, async: false, data: { 'UserName': 'admin', 'Password': 'XXXXXXXXX', '__RequestVerificationToken': objval, 'RememberMe': false }, success: function (data, status, xhr) { alert("ok"); console.log(xhr.getAllResponseHeaders()); } }); } });
这里猜测,估计是因为浏览器的安全机制,拦截了我的ajax
那么我就换C#模拟http请求,可以得到我要的API返回的结果,代码如下:
//发送请求 using (HttpClient client = new HttpClient()) { client.BaseAddress = new Uri("http://192.168.0.225:8082"); try { //在没有登录之前,不管写什么路径,都会跳到login页面,而所写的路径会自动添加到returnurl这个参数后 //登录之后就会跳转到这个页面 //这一步是为了拿到__RequestVerificationToken var cliPost = client.GetAsync("/warehouseapi/admin/index"); cliPost.Wait(); var result = cliPost.Result; string resultContent = result.Content.ReadAsStringAsync().Result; //返回了一个Document对象,用NSoup解析,NSoup可以用像jQuery那样的选择器选择元素 var htmlDoc = NSoup.NSoupClient.Parse(resultContent); var __RequestVerificationToken = htmlDoc.Select("[name=__RequestVerificationToken]").Val(); var actionPath = htmlDoc.Select(".form-horizontal").Attr("action"); //准备Post的参数 var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("UserName", "admin"), new KeyValuePair<string, string>("Password", "ZXXXXXXXX@"), new KeyValuePair<string, string>("RememberMe", "false"), new KeyValuePair<string, string>("__RequestVerificationToken", __RequestVerificationToken), }); try { //发送请求 //登录 var cliPost1 = client.PostAsync(actionPath, content); cliPost1.Wait(); var result1 = cliPost1.Result; string resultContent1 = result.Content.ReadAsStringAsync().Result; //向API发送请求 var cliGet = client.GetAsync("/warehouseapi/admin/index"); cliGet.Wait(); var resultGet = cliGet.Result; string resultContent2 = resultGet.Content.ReadAsStringAsync().Result; Console.WriteLine(DateTime.Now + "======" + resultContent2); } catch (Exception ex) { Console.WriteLine(ex.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } Console.Read();
补充:上面是get请求方式,如果是post请求的时候,由于框架有ValidateAntiForgeryToken这个的验证,所以每次请求都要带上一个__RequestVerificationToken参数。
ValidateAntiForgeryToken的用途是防止CSRF(跨网站请求伪造),这个具体的可以百度了解。
总之每次post请求一个api路径都要带上__RequestVerificationToken参数,而__RequestVerificationToken参数是基于上一次请求返回的html页面中[name="__RequestVerificationToken"]的hidden标签的值,这个值每次是不一样的。
post请求的示例如下:
//发送请求 using (HttpClient client = new HttpClient()) { client.BaseAddress = new Uri("http://192.168.0.225:8082"); try { //在没有登录之前,不管写什么路径,都会跳到login页面,而所写的路径会自动添加到returnurl这个参数后 //登录之后就会跳转到这个页面 //这一步是为了拿到__RequestVerificationToken var cliPost = client.GetAsync("/Login?ReturnUrl=%2Fadmin"); cliPost.Wait(); var result = cliPost.Result; string resultContent = result.Content.ReadAsStringAsync().Result; //返回了一个Document对象,用NSoup解析,NSoup可以用像jQuery那样的选择器选择元素 var htmlDoc = NSoup.NSoupClient.Parse(resultContent); var __RequestVerificationToken = htmlDoc.Select("[name=__RequestVerificationToken]").Val(); var actionPath = htmlDoc.Select(".form-horizontal").Attr("action"); //准备Post的参数 用户名、密码错误会跳回登录页 var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("UserName", "wXXXXXi"), new KeyValuePair<string, string>("Password", "XXXXXXXX21"), new KeyValuePair<string, string>("RememberMe", "false"), new KeyValuePair<string, string>("__RequestVerificationToken", __RequestVerificationToken), }); try { //发送请求 //登录 var cliPost1 = client.PostAsync(actionPath, content); cliPost1.Wait(); var result1 = cliPost1.Result; string resultContent1 = result1.Content.ReadAsStringAsync().Result; //返回了一个Document对象,用NSoup解析,NSoup可以用像jQuery那样的选择器选择元素 var htmlDoc1 = NSoup.NSoupClient.Parse(resultContent1); var __RequestVerificationToken1 = htmlDoc1.Select("[name=__RequestVerificationToken]").Val(); var actionPath1 = htmlDoc1.Select(".form-horizontal").Attr("action"); //var cliGet = client.GetAsync("/warehouseapi/admin/index"); //cliGet.Wait(); //var rs = cliGet.Result; //string rscontent = rs.Content.ReadAsStringAsync().Result; //向API发送请求 DeliveryRecords drc = new DeliveryRecords() { CustCode = "001", LogisticCode = "SUER001", Express = "速尔", OutNo = "abc01", Assistant = "何助理", DanQty = "1", JianQty = "13", SendDate = "2018-11-22" }; string rcJsonStr = JsonConvert.SerializeObject(drc); var rcContent = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("__RequestVerificationToken", __RequestVerificationToken1), new KeyValuePair<string, string>("rcJson", rcJsonStr) }); var cliRcPost = client.PostAsync("/warehouseapi/admin/InsertDeliRecoAsync", rcContent); cliRcPost.Wait(); var rs = cliRcPost.Result; var rsStr = rs.Content.ReadAsStringAsync().Result; Console.WriteLine(rsStr); } catch (Exception ex) { Console.WriteLine(ex.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } Console.Read();