問題描述
在爬取某網站的時候遇到了問題,因爲網站的避免CSRF攻擊機制,無法獲取到目標頁面數據,而是跳轉到一個默認頁面。
關於CSRF
1、CSRF tokens是如何工作(詳情請點擊查引用源站點)
1、服務器發送給客戶端一個token。
2、客戶端提交的表單中帶着這個token。
3、如果這個token不合法,那麼服務器拒絕這個請求。
2、java 站點應對CSRF參考(詳情請點擊查引用源站點)
3、Laravel中如何避免CSRF攻擊(詳情請點擊查引用源站點)
爬取站點分析
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標籤的起作用,要是應對不同的站點,需要因用不同的對策獲取