利用Java编码北京PK10平台制作测试CSRF令牌验证的Web API

拙文是利用了Jmeter来测试北京PK10平台制作(www.1159880099.com)QQ1159880099 带有CSRF令牌验证的Web API;最近几天趁着项目不忙,练习了用编码的方式实现。

有了之前Jmeter脚本的基础,基本上难点也就在两个地方:获取CSRF令牌、Cookie的传递。

首先添加依赖,在POM.xml中添加以下内容:

    <!-- https:// mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.6</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.11.3</version>
    </dependency>

解释作用:

  • httpClient:用来创建httpClient、管理Get和Post的方法、获取请求报文头、应答报文内容、管理CookieStore等等;
  • jsoup:用来解析应答报文,获得CSRF令牌的值。

创建一个Web API测试类:

public class LoginEHR {

private final static String EHR_ADDRESS = "http://ourTestEHRServer:8083";

static BasicCookieStore cookieStore = new BasicCookieStore();
static CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();


我选择了CookieStore的方式管理会话;HttpClient现在还有另一种Context的方式实现会话持久,以后再做深入研究。

先写一个打印应答报文的方法,并不做什么处理,纯打印;根据实际需要调用或者注释:

public class LoginEHR {

private static void printResponse(HttpResponse httpResponse)

throws ParseException, IOException {
// 获取响应消息实体
HttpEntity entity = httpResponse.getEntity();
// 响应状态
System.out.println("--------Status: " + httpResponse.getStatusLine());
System.out.println("--------Headers: ");
HeaderIterator iterator = httpResponse.headerIterator();
while (iterator.hasNext()) {
System.out.println("\t" + iterator.next());
}
// 判断响应实体是否为空
if (entity != null) {
String responseString = EntityUtils.toString(entity);
System.out.println("--------Response length: " + responseString.length());
System.out.println("--------Response content: "

  • responseString.replace("\r\n", ""));
    }
    }

    现在开始写测试方法,虽然篇幅较长,仍然写在main()方法里,便于展示:

public class LoginEHR {

private final static String EHR_ADDRESS = "http://ourTestEHRServer:8083";

static BasicCookieStore cookieStore = new BasicCookieStore();
static CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();

public static void main(String[] args) throws Exception {

    String username = "00022222";
    String password = "abc123456";

    HttpResponse httpResponse = null;

    try {
        HttpGet httpGet = new HttpGet(EHR_ADDRESS);
        httpResponse = httpClient.execute(httpGet);
        System.out.println("--------Cookie store for the 1st GET: " + cookieStore.getCookies());
        // 唯一的作用是打印应答报文,没有任何处理;实际测试时,可以不执行

// printResponse(httpResponse);

        // 取出第一次请求时,服务器端返回的JSESSIONID;
        // 实际上此处只是取出JSESSIONID用作打印;cookieStore自动保存了本次会话的Cookie信息

// List cookies = cookieStore.getCookies();
// String cookie = cookies.toString();
// String sessionID = cookie.substring("[[version: 0][name: JSESSIONID][value: ".length(),
// cookie.indexOf("][domain"));
// System.out.println("--------The current JSESSIONID is: " + sessionID);

        httpClient.close();
    } catch (Exception ex) {
        ex.printStackTrace();
    }

}

private static void printResponse(HttpResponse httpResponse)
throws ParseException, IOException { ...... }

根据之前Jmeter测试脚本的经验,先发送一次Get请求,从应答报文中得到CSRF令牌和JSESSIONID。

大家注意我注释掉的那几行打印JSESSIONID的代码,之前在没有引入CookieStore之前,我想的是自己写一个新的Cookie,并把它赋给后面几次请求。

当使用CookieStore之后,就不需要自己封装Cookie、以及添加到Request的Header了,这过程会自动完成。没有删掉也是为了需要的时候打印。

交代完Cookie之后,该轮到处理CSRF令牌了。如果打印出第一次Get的应答,我们能看到令牌的格式是如下呈现的:

之前在Jmeter脚本中,我是添加了一个正则表达式提取器,把_csrf的content提取出来。

现在我将用jsoup来解析和返回content的内容,代码如下:

private static String getCsrfToken(HttpEntity responseEntity) throws IOException{
//获取网页内容,指定编码
String web = EntityUtils.toString(responseEntity,"utf-8");
Document doc= Jsoup.parse(web);
// 选择器,选取特征信息
String token = doc.select("meta[name=_csrf]").get(0).attr("content");
System.out.println( "--------The current CSRF Token is: " + token);

    return token;
}

在main()中调用此方法:

        // 利用Jsoup从应答报文中读取CSRF Token
        HttpEntity responseEntity = httpResponse.getEntity(); 

       String token = getCsrfToken(responseEntity);
然后再封装POST的请求内容:

        // 获取到CSRF Token后,用Post方式登录
        HttpPost httpPost = new HttpPost(EHR_ADDRESS);

        // 拼接Post的消息体
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        nvps.add(new BasicNameValuePair("username", username));
        nvps.add(new BasicNameValuePair("password", password));
        nvps.add(new BasicNameValuePair("_csrf", token));
        HttpEntity loginParams = new UrlEncodedFormEntity(nvps, "utf-8");
        httpPost.setEntity(loginParams);

        // 第二次请求,带有CSRF Token
        httpResponse = httpClient.execute(httpPost);

// System.out.println("--------Cookie store for the POST: " + cookieStore.getCookies());
printResponse(httpResponse);
然后。。。这里发生了一点小意外:

按照设想,应该能跳转到登录成功、或者验证失败的页面;而Post方法执行后,从服务器返回的状态码是302,被跳转到另一个网址。

如果放任不管,直接提交后面的业务查询,是不会得到成功的;执行的结果是又回到了登录页面。

我在网上爬了一会,发现提问Post得到301、302的人还不在少数,说明这个坑还是给很多人造成了困扰。

简单的说,如果得到了服务器重定向到新的地址,我们也要跟着执行一次新地址的访问;否则服务器会认为这次请求没有得到正确处理,即便我之后的请求带着全套的验证令牌和Cookie,也会被拦截在系统外。

有了这个认识,下面我需要完成的就是对Code:302的处理;添加代码如下:

        // 取POST方法返回的HTTP状态码;不出意外的话是302
        int code = httpResponse.getStatusLine().getStatusCode();
        if (code == 302) {
            Header header = httpResponse.getFirstHeader("location"); // 跳转的目标地址是在 HTTP-HEAD 中的
            String newUri = header.getValue(); // 这就是跳转后的地址,再向这个地址发出新申请,以便得到跳转后的信息是啥。
            // 实际打印出来的是接口服务地址,不包括IP Address部分
            System.out.println("--------Redirect to new location: " + newUri);
            httpGet = new HttpGet(EHR_ADDRESS + newUri);

            httpResponse = httpClient.execute(httpGet);

// printResponse(httpResponse);
}
这里需要注意的地方是跳转的location内容。在我这里,服务器给的只是一个单词【/work】,最好加一个打印的步骤。

确认不是一个完整的URL之后,需要把链接拼完整,然后进行一次httpGet请求。

这个httpGet执行之后,我可以确认已经登录成功(或者,又被送回登录页面,当然我这里是成功了)。

接下来是提交一次业务查询的Get,确认能够在系统中进行业务操作:

        // 请求一次绩效;确认登录成功
        String queryUrl = EHR_ADDRESS + "/emp/performance/mt/query";
        httpGet = new HttpGet(queryUrl);
        httpResponse = httpClient.execute(httpGet);
        System.out.println("--------Result of the Cardpunch Query: ");
        printResponse(httpResponse);

最后确认查询的结果无误后,整个脚本完成;只需要修改最后的业务查询,就可以生成其他的测试脚本了。

完整的源码如下:

package com.jason.apitest;

import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class LoginEHR {

private final static String EHR_ADDRESS = "http://ourTestEHRServer:8083";

static BasicCookieStore cookieStore = new BasicCookieStore();
static CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();

public static void main(String[] args) throws Exception {

    String username = "00022222";
    String password = "abc123456";

    HttpResponse httpResponse = null;

    try {
        HttpGet httpGet = new HttpGet(EHR_ADDRESS);
        httpResponse = httpClient.execute(httpGet);
        System.out.println("--------Cookie store for the 1st GET: " + cookieStore.getCookies());
        // 唯一的作用是打印应答报文,没有任何处理;实际测试时,可以不执行

// printResponse(httpResponse);

        // 取出第一次请求时,服务器端返回的JSESSIONID;
        // 实际上此处只是取出JSESSIONID用作打印;cookieStore自动保存了本次会话的Cookie信息

// List cookies = cookieStore.getCookies();
// String cookie = cookies.toString();
// String sessionID = cookie.substring("[[version: 0][name: JSESSIONID][value: ".length(),
// cookie.indexOf("][domain"));
// System.out.println("--------The current JSESSIONID is: " + sessionID);

        // 利用Jsoup从应答报文中读取CSRF Token
        HttpEntity responseEntity = httpResponse.getEntity();
        String token = getCsrfToken(responseEntity);

        // 获取到CSRF Token后,用Post方式登录
        HttpPost httpPost = new HttpPost(EHR_ADDRESS);

        // 拼接Post的消息体
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        nvps.add(new BasicNameValuePair("username", username));
        nvps.add(new BasicNameValuePair("password", password));
        nvps.add(new BasicNameValuePair("_csrf", token));
        HttpEntity loginParams = new UrlEncodedFormEntity(nvps, "utf-8");
        httpPost.setEntity(loginParams);

        // 第二次请求,带有CSRF Token
        httpResponse = httpClient.execute(httpPost);

// System.out.println("--------Cookie store for the POST: " + cookieStore.getCookies());
printResponse(httpResponse);

        // 取POST方法返回的HTTP状态码;不出意外的话是302
        int code = httpResponse.getStatusLine().getStatusCode();
        if (code == 302) {
            Header header = httpResponse.getFirstHeader("location"); // 跳转的目标地址是在 HTTP-HEAD 中的
            String newUri = header.getValue(); // 这就是跳转后的地址,再向这个地址发出新申请,以便得到跳转后的信息是啥。
            // 实际打印出来的是接口服务地址,不包括IP Address部分
            System.out.println("--------Redirect to new location: " + newUri);
            httpGet = new HttpGet(EHR_ADDRESS + newUri);

            httpResponse = httpClient.execute(httpGet);

// printResponse(httpResponse);
}

        // 请求一次绩效;确认登录成功
        String queryUrl = EHR_ADDRESS + "/emp/performance/mt/query";
        httpGet = new HttpGet(queryUrl);
        httpResponse = httpClient.execute(httpGet);
        System.out.println("--------Result of the Cardpunch Query: ");
        printResponse(httpResponse);

        httpClient.close();
    } catch (Exception ex) {
        ex.printStackTrace();
    }

}

private static void printResponse(HttpResponse httpResponse)
        throws ParseException, IOException {
    // 获取响应消息实体
    HttpEntity entity = httpResponse.getEntity();
    // 响应状态
    System.out.println("--------Status: " + httpResponse.getStatusLine());
    System.out.println("--------Headers: ");
    HeaderIterator iterator = httpResponse.headerIterator();
    while (iterator.hasNext()) {
        System.out.println("\t" + iterator.next());
    }
    // 判断响应实体是否为空
    if (entity != null) {
        String responseString = EntityUtils.toString(entity);
        System.out.println("--------Response length: " + responseString.length());
        System.out.println("--------Response content: "
                + responseString.replace("\r\n", ""));
    }
}

private static String getCsrfToken(HttpEntity responseEntity) throws IOException{
    //获取网页内容,指定编码
    String web = EntityUtils.toString(responseEntity,"utf-8");
    Document doc= Jsoup.parse(web);
    // 选择器,选取特征信息
    String token = doc.select("meta[name=_csrf]").get(0).attr("content");
    System.out.println( "--------The current CSRF Token is: " + token);

    return token;
}

}

原文地址:http://blog.51cto.com/13927860/2161225

时间: 2024-10-01 21:14:26

利用Java编码北京PK10平台制作测试CSRF令牌验证的Web API的相关文章

利用Jmeter测试CSRF令牌验证的Web API

事情的起因是最近收到的一批测试需求,要测试公司HR系统的接口性能.这个是需要测试的接口列表: 所有的接口请求,都基于登录验证成功,否则将无法获得正确的应答. 首先想到的是在浏览器上捕捉请求.打开Chrome浏览器,调出开发者工具栏,在地址栏输入登录模块的地址,访问登录页面: 输入账号和密码,录制登录过程:然后定位到开发工具的Network页面,找到登录的事务.如下图: 注意右下方的Form Data,这是登录POST方法提交的三个参数,我们需要捕捉的就是_csrf的那个动态令牌. 通过在网上的一

Java内部类北京PK10平台出租的使用小结

为什么要北京PK10平台出租(www.1159880099.com)QQ1159880099 使用内部类:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响.内部类:顾名思义就是把类放在某个范围里类可以放在包中类可以放在文件夹中类可以放在类中 //内部类类可以放在方法中 //内部类 一.成员内部类类中放置内部类,类里套类,类中类要使用内部类,就需要在外部类中实例化内部类的对象,然后通过对象打点调用内部类中的

JAVA基础知识之北京pk-10平台出租流

一.北京pk-10平台出租Q1446595067流解释API文档说明:FileReader流是用于读取字符文件的便捷类.此类的构造函数假定默认字符编码和默认字节缓冲区大小是合适的.如果要自己指定这些值,那么需要通过FileInputStream流对象来构造InputStreamReader流对象即可 FileReader类继承InputStreamReader类 public class FileReader extends InputStreamReader{}1)构造函数假定默认字符编码和默

SpringMVC请求参数北京PK10平台出租和响应结果全局加密和解密

前段时间在做一个对外的网关项目,涉及到加密和解密模块,这里详细分析解决方案和适用的场景.为了模拟真实的交互场景,先定制一下整个交互流程.第三方传输(包括请求和响应)数据报文包括三个部分: 1.timestamp,long类型,时间戳.2.data,String类型,实际的业务请求数据转化成的Json字符串再进行加密得到的密文.3.sign,签名,生成规则算法伪代码是SHA-256(data=xxx&timestamp=11111),防篡改.为了简单起见,加密和解密采用AES,对称秘钥为"

Android recycleView 的一北京PK10平台出租些优化与相关问题

北京PK10平台出租论坛:haozbbs.com Q1446595067 recycleView 也出来很长时间了,记录一些自己见到的recycleView优化吧. 1.recyclerView.setHasFixedSize(true); Item的高度是固定的,设置这个选项可以提高性能.总得来说就是就是避免整个布局绘制.就是避免requestLayout. 具体的可以看看stackoverflow的这个几个问题 Understanding RecyclerView setHasFixedSi

北京PK10平台出租matlab的快捷方式与使用技巧

Matlab常用小技巧一:北京PK10平台出租(www.1159880099.com)QQ1159880099 1. m文件如果是函数,保存的文件名最好与函数名一致,这点都很清楚.不过容易疏忽的是,m文件名的命名尽量不要是简单的英文单词,最好是由大小写英文/数字/下划线等组成.原因是简单的单词命名容易与matlab内部函数名同名,结果会出现一些莫名其妙的错误.例如,写个m文件,命名为spy,运行时就弹出一个怪怪的figure,呵呵,我当初还以为是什么bug. 2. 调试程序时,经常要屏蔽掉一整段

北京PK10平台架设 href=&quot;javascript:void(0)&quot;的用法

href="javascript:void(0)"的用法 href="javascript:void(0)"的理解 href="javascript:void(0);"的含义是,让超链接去执行一个js函数,而不是去跳转到一个地址,而void(0)表示一个空的方法,也就是不执行js函数.北京PK10平台架设 Q-2189563389为什么要使用href="javascript:void(0);" javascript:是伪协议,

北京PK10平台搭建MySQL服务器

MySQL是一个开放源码的小型关联式数据库管理系统,北京PK10平台搭建Q-2633534051,由于其体积小,速度快,总体拥有成本低,被广泛的做的网站数据库,目前主流的网站架构为LAMP(linux + apache + mysql + php)和 LNMP( linux + nginx + mysql + php ) MySQL 有两在引擎:MyISAM 特点:强调性能,比 innoDB 快,但不提供事务支持,适合执行大量 SELECT(查询)操作. innoDB 特点: 提供事务支持事务,

国内通信巨头北京PK10源码出售的物联网平台建设如何?

物联网平台处在物联网四层架构的平台层北京PK10源码出售 <企鹅> 2952777280[链接] huaxianym.com ,物联网平俨然已经成为产业链的关键环节.其作用主要体现在四个方面: 优化管理——通过物联网平台,企业可以为产品配备远程控制和实时监控功能.增加可配置的警报和通知.可插入的云服务,并且实现与消费者的智能手机和其他设备的集成. 优化成本——通过物联网平台,企业可以实时进行生产分析,进而完成预测性维护.生产管理.工艺流程优化等目标,最终在降本节能上体现成果. 优化开发——物联