利用Java編碼北京PK10平台製作測試CSRF令牌驗證的Web API

來源:互聯網
上載者:User

標籤:請求報文   其他   自己   des   string   prot   行業   從伺服器   post方法   

拙文是利用了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指令碼中,我是添加了一個Regex提取器,把_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;}

}

利用Java編碼北京PK10平台製作測試CSRF令牌驗證的Web API

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.