Java 關於爬取網站資料遇到csrf-token的分析與解決
阿新 • • 發佈:2019-02-16
問題描述
在爬取某網站的時候遇到了問題,因為網站的避免CSRF攻擊機制,無法獲取到目標頁面資料,而是跳轉到一個預設頁面。
關於CSRF
1、伺服器傳送給客戶端一個token。
2、客戶端提交的表單中帶著這個token。
3、如果這個token不合法,那麼伺服器拒絕這個請求。
爬取站點分析
1、在http頭資訊可見X-CSRF-Token欄位
2、X-CSRF-Token來源,meta標籤截圖
解決思路
1、獲取請求前,先去請求一次這個連結地址的Host,在這一次請求中獲取Token,以及Cookie
2、用獲取到的Token和Cookie作為頭資訊去請求傳入連結地址
3、相當於多請求了一次這個請求連結地址的Host,必須要注意的是確定獲取的Host返回頭資訊中包含有Set-Cookie欄位,以及返回的內容中有包含Token的mate標籤,存在這兩個條件的時候,這個解決流程才可能實現
實現程式碼
1、獲取Cookie與Token
package dto.ajax;
import org.apache.commons.lang3.StringUtils;
import tool.GetUrlData;
import tool.RegexFinder;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
/**
* Created by yuyu on 2018/3/23.
* 獲取處理CSRF需要的相關資訊
* 原理:
* 每個使用者Session生成了一個CSRF Token,
* 該Token可用於驗證登入使用者和發起請求者是否是同一人,
* 如果不是則請求失敗。
*/
public class CsrfToken {
String cookie;//用於請求的站點的cokie
String csrf_token;//用於請求站點的金鑰
/**
* 接收一個網站的host,設定介面請求需要的資料
* 1、獲取網站請求回來的Set-Cookie欄位
* 2、然後獲取內容中的金鑰
* <meta content="金鑰" name="csrf-token" />
*
* @param url
*/
public CsrfToken(String url) {
//校驗傳輸安全
if(StringUtils.isNotBlank(url)){
try{
//設定請求的頭資訊.獲取url的host
String host=new URL(url).getHost();
URLConnection connection= GetUrlData.getConnection("http://"+host,null);
//獲取請求回來的資訊
String data = GetUrlData.getStringByConnection(connection);
//匹配token
String metaRegex="<meta(.*?)\\/>";
String tokenRegex="content=\"(.*?)\"";
String tokenReplace="content=\"|\"";
String tokenName="csrf-token";//mate中的名稱
//獲取mate頭資訊
List<String> mate=RegexFinder.getAllToList(metaRegex ,data);
for (String info :mate){
if (info.indexOf(tokenName)>0){
//取出對應的金鑰
this.csrf_token=RegexFinder.findOneByReplaceEmpty(tokenRegex
,info,tokenReplace);
}
}
//獲取cookie頭資訊
this.cookie = connection.getHeaderField("Set-Cookie");
//胡羅內容以外的欄位
String regexCookie="\\S+=(.*?);";
if (this.cookie!=null){
this.cookie=RegexFinder.findOne(regexCookie,this.cookie);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public String getCookie() {
return cookie;
}
public void setCookie(String cookie) {
this.cookie = cookie;
}
public String getCsrf_token() {
return csrf_token;
}
public void setCsrf_token(String csrf_token) {
this.csrf_token = csrf_token;
}
}
2、正則工具類
package tool;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by yy on 2017/12/19.
* 用於正則的工具類
*/
public class RegexFinder {
/**
* 匹配一個數據與正則,返回匹配的資料
*
* @param regex
* @param info
* @return
* @throws Exception
*/
public static String findOne(String regex,String info){
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(info);
if(matcher.find()){
return matcher.group();
}
return null;
}
/**
* 匹配一個數據與正則,返回匹配的資料
*
* @param regex
* @param info
* @return
* @throws Exception
*/
public static String findOneByReplaceEmpty(String regex,String info,
String replace){
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(info);
if(matcher.find()){
return matcher.group().replaceAll(replace,"");
}
return null;
}
/**
* 獲取正則匹配全部可能,到list資料
* @param regex
* @param info
* @return 成功返回一個
*/
public static List<String> getAllToList(String regex,String info){
//資料安全校驗
if(StringUtils.isEmpty(regex)||StringUtils.isEmpty(info)){
return null;
}
List<String> back=new ArrayList<String>();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(info);
while (matcher.find()){
back.add(matcher.group());
}
return back;
}
/**
* 獲取正則匹配全部可能,到list資料,替換字元中得資料
* @param regex
* @param info
* @param replace
* @return
*/
public static List<String> getAllToListByReplaceEmpty(String regex,String info
, String replace) {
//資料安全校驗
if(StringUtils.isEmpty(regex)||StringUtils.isEmpty(info)){
return null;
}
List<String> back=new ArrayList<String>();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(info);
while (matcher.find()){
back.add(matcher.group().replaceAll(replace,""));
}
return back;
}
}
3、爬取呼叫邏輯
package tool;
import dto.ajax.CsrfToken;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
/**
* Created by yy on 2017/10/17.
* 獲取url資訊的工具類
*/
public class GetUrlData {
/**
* 破解X-CSRF-Token 跨站請求偽造限制
* @param url
* @return
* @throws Exception
*/
public static String getX_CSRF_Token(String url) throws Exception{
CsrfToken csrfToken=new CsrfToken(url);
//設定頭資訊
Map<String,String> args=new HashMap<String, String>();
args.put("Cookie",csrfToken.getCookie());
args.put("X-CSRF-Token",csrfToken.getCsrf_token());
args.put("X-Requested-With","XMLHttpRequest");
args.put("Accept","application/json, text/javascript, */*; q=0.01");
URLConnection connection=GetUrlData.getConnection(url,args);
//獲取資料
return GetUrlData.getStringByConnection(connection);
}
/**
* 根據傳入的資料獲取URLConnection物件
* @param url
* @param mapArgs
* @return
* @throws Exception
*/
public static URLConnection getConnection(String url, Map<String,String> mapArgs)throws Exception{
//設定請求的頭資訊
URL urlInfo = new URL(url);
URLConnection connection = urlInfo.openConnection();
//設定傳入的頭資訊
if (mapArgs!=null){
for(String key:mapArgs.keySet()){
connection.addRequestProperty(key,mapArgs.get(key));
}
}
//設定預設頭資訊
connection.addRequestProperty("Host", urlInfo.getHost());
connection.addRequestProperty("Connection", "keep-alive");
connection.addRequestProperty("Cache-Control", "max-age=0");
connection.addRequestProperty("Upgrade-Insecure-Requests", "1");
//表示使用者不願意目標站點追蹤使用者個人資訊。
connection.addRequestProperty("DNT", "1");
//強制要求快取伺服器在返回快取的版本之前將請求提交到源頭伺服器進行驗證。
connection.addRequestProperty("Pragma", "no-cache");
connection.addRequestProperty("Accept", "*/*");
connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36");
connection.addRequestProperty("Referer", "http://"+urlInfo.getHost());
connection.connect();
return connection;
}
/**
* 將獲取的連結讀取對應的資料
* @param connection
* @return
*/
public static String getStringByConnection(URLConnection connection) throws Exception{
//定義返回資料的格式
InputStreamReader input = new InputStreamReader(connection.getInputStream(),"UTF-8");
BufferedReader reader = new BufferedReader(input);
StringBuilder data = new StringBuilder();
String str;
while ((str = reader.readLine()) != null) {
data.append(str);
}
//關閉操作流
reader.close();
input.close();
return data.toString();
}
}
4,測試程式碼
@Test
public void testGet(){
String url = "需要請求的地址";
try{
System.out.println(GetUrlData.getX_CSRF_Token(url));
}catch (Exception e){
e.printStackTrace();
}
}
執行結果
1、正常獲取時無法獲取目標頁面,拿到的時預設頁面資料截圖
2、執行以上的程式碼時獲取的資料截圖
總結
1、不同的站點有不同的處理機制,所以以上的程式碼只是一個解決的思路,並不是適合所有的爬取
2、有些站點請求的Host並不返回Set-Cookie欄位,這時候需要找到返回該欄位的請求來獲取
3、以上的程式碼只對於特定的mate標籤的起作用,要是應對不同的站點,需要因用不同的對策獲取