Java 和 HTTP 的那些事(三) 代理認證
前面一篇部落格介紹了在 Java 中使用 HttpURLConnection
和 HttpClient
通過代理訪問
HTTP 站點的方法,但是可以看到程式碼中使用的代理都是免費公開的代理,不需要使用者名稱密碼就能直接訪問。由於網際網路上公開的代理安全性不能保證,這種代理隨便用用即可,如果要慎重起見,我推薦大家還是自己搭建代理伺服器。但是有一點要特別注意,如果自己搭建代理伺服器的話,一定不要公開,要設定使用者名稱密碼,一般情況下,我們使用簡單的基本身份認證就可以了。如果你不設定密碼的話,沒過幾天你就會發現你的伺服器會卡到爆,登上去使用 netstat 一看,幾百上千個連線,伺服器頻寬全佔滿了。這是因為網際網路上有著大量的代理掃描程式在沒日沒夜的掃描,你搭建的代理伺服器沒設密碼,或者弱密碼,都會被掃出來,而掃出來的後果就是,你的代理伺服器被公開到各大免費代理站點,然後所有人都來連你的代理伺服器,直到把你的頻寬流量耗盡。
一、關於 HTTP 的身份認證
我們這裡給代理伺服器設定了使用者名稱和密碼之後,無論在程式中,還是在瀏覽器裡使用該代理時,都需要進行身份認證了。HTTP 協議最常見的認證方式有兩種:基本認證(Basic authentication)和摘要認證(Digest authentication)。HTTP 的認證模型非常簡單,就是所謂的質詢/響應(challenge/response)框架:當用戶向伺服器傳送一條 HTTP 請求報文時,伺服器首先回復一個“認證質詢”響應,要求使用者提供身份資訊,然後使用者再一次傳送 HTTP 請求報文,這次的請求頭中附帶上身份資訊(使用者名稱密碼),如果身份匹配,伺服器則正常響應,否則伺服器會繼續對使用者進行質詢或者直接拒絕請求。
摘要認證的實現比基本認證要複雜一點,在平時的使用中也並不多見,這裡忽略,如果想詳細瞭解它,可以檢視維基百科上關於 HTTP 摘要認證 的解釋。這裡重點介紹下 HTTP 基本認證,因為無論是代理伺服器對使用者進行認證,還是 Web 伺服器對使用者進行認證,最常用的手段都是 HTTP 基本認證,它實現簡單,容易理解,幾乎所有的伺服器都能支援它。
一個典型的 HTTP 基本認證,如下圖所示,圖片摘自《HTTP 權威指南》:
使用者第一次向伺服器發起請求時,伺服器會返回一條 401 Unauthorized 響應,如果使用者是使用瀏覽器訪問的話,瀏覽器會彈出一個密碼提示框,提醒使用者輸入使用者名稱和密碼,於是使用者重新發起請求,在第二次請求中將在 Authorization 頭部新增上身份資訊,這個身份資訊其實只是簡單的對使用者輸入的使用者名稱密碼做了
二、使用基本認證
2.1 區分 Proxy 認證 和 WWW 認證
這篇部落格本來是介紹代理認證的,但是代理認證其實只是 HTTP 身份認證中的一種而已,所以上面大部分內容對於代理認證來說是一樣的,包括質詢/響應框架以及身份認證的基本流程。不過要在程式碼裡實現這兩種認證,細節方面會有所不同,下面是兩種認證的一個對比。
- 根本區別
- WWW 認證:指的是 Web 伺服器對客戶端發起的認證
- Proxy 認證:指的是代理伺服器對客戶端發起的認證
- 響應的狀態碼不同
- WWW 認證:第一次訪問時響應 401 Unauthorized
- Proxy 認證:第一次訪問時響應 407 Unauthorized
- 認證頭部不同
- WWW 認證:WWW-Authenticate, Authorization, Authentication-Info
- Proxy 認證:Proxy-Authenticate, Proxy-Authorization, Proxy-Authentication-Info
2.2 手工設定認證頭部
通過上面的介紹我們瞭解到,要實現 HTTP 身份認證,無論是 WWW 認證也好,Proxy 認證也罷,其實只需要在 HTTP 的請求頭部新增一個認證的頭部(Authorization
或者 Proxy-Authorization
)。認證頭部的資訊就是使用者名稱和密碼,將使用者名稱和密碼使用冒號分割,然後再對其進行
Base64 編碼即可,我們使用 HttpURLConnection
來模擬這個過程,如下:
1 2 3 4 5 6 7 8 9 10 11 12 | URL obj =newURL(url); HttpURLConnection con = (HttpURLConnection) obj.openConnection(); // 設定認證頭部 finalString userName = "username"; finalString password = "password"; String nameAndPass = userName +":"+ password; String encoding =newString(Base64.encodeBase64(nameAndPass.getBytes())); con.setRequestProperty("Authorization","Basic " + encoding); con.setRequestMethod("GET"); String responseBody = readResponseBody(con.getInputStream()); |
如果是代理認證的話,設定頭部的程式碼改成下面這樣:
1 | con.setRequestProperty("Proxy-Authorization","Basic " + encoding); |
這種方式最為原始,也最為簡單直白,幾乎沒什麼好解釋的。
但是在我使用這種方式來訪問 HTTPS 站點的時候卻遇到了問題:第一種情況是訪問需要進行基本身份認證的 HTTPS 站點,測試通過;第二種情況是訪問 HTTPS 站點,通過一個代理,代理需要進行基本身份認證,測試失敗,返回下面的錯誤:
java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required"
at sun.net.www.protocol.http.HttpURLConnection.doTunneling(HttpURLConnection.java:2085)
at sun.net...https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:183)
我的第一直覺是通過代理訪問 HTTPS 站點時,代理的認證資訊應該沒有發出去,果不其然,使用 Wireshark 截獲了兩次請求的資料包,第二次請求裡沒有我們加的 Proxy-Authorization
頭部。
在 Google 上搜索這個問題,發現早在 2000 年(那可是 16 年前,當時 Java 的版本還是 1.0 呢)就有人在 JDK 的 bug database 裡提交了這個問題(JDK-4323990),看這個問題的更新狀態應該是在
JDK 1.4 版本里已經修復了,但是為啥我這裡還是測試不通過呢!
下面是測試的完整程式碼,始終不理解為什麼通不過,還需要繼續研究下 HttpURLConnection
中的實現細節,如果有高人知道,還請多多指教。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Test publicvoidbasicHttpsGetWithProxyNeedAuthUsingBase64Basic()throwsException { Proxy proxy = newProxy(Proxy.Type.HTTP,newInetSocketAddress("139.132.22.90",8213)); finalString proxyUserName = "username"; finalString proxyPassword = "password"; URL obj = newURL(url); HttpsURLConnection con = (HttpsURLConnection) obj.openConnection(proxy); String nameAndPass = proxyUserName + ":"+ proxyPassword; String encoding = newString(Base64.encodeBase64(nameAndPass.getBytes())); con.setRequestProperty("Proxy-Authorization","Basic " + encoding); con.setRequestMethod("GET"); String responseBody = readResponseBody(con.getInputStream()); System.out.println(responseBody); } |
2.3 實現 Authenticator
自己手工設定認證頭部雖然簡單,而且在某些情況下可以達到意想不到的效果。但是使用這種方法可能會出現問題(像上面提到的訪問 HTTPS 站點時遇到的問題),而且 Proxy 認證和 WWW 認證這兩種情形還需要分別處理,不是很方便。除了可以自己手工拼 HTTP 請求頭部之外,其實還有另一種更簡單的方法,那就是 java.net 提供的 Authenticator
類。Authenticator
類是一個抽象類,必須先定義一個類來繼承它,然後重寫它的 getPasswordAuthentication()
這個方法,定義新類比較繁瑣,我們可以直接使用匿名類,如下:
1 2 3 4 5 6 | Authenticator authenticator =newAuthenticator() { publicPasswordAuthentication getPasswordAuthentication() { returnnewPasswordAuthentication(userName, password.toCharArray()); } }; Authenticator.setDefault(authenticator); |
請求部分程式碼還是一樣,如果使用代理,openConnection()
方法就加個代理引數。這種方式不區分是
Proxy 認證還是 WWW 認證,如果是 Proxy 認證,userName 和 password 就設定成代理的使用者名稱密碼,如果是 WWW 認證,則設定成 Web 伺服器認證的使用者名稱密碼。
要特別注意的一點是,通過這種方式設定認證方式是 JVM 全域性的,同一個 JVM 下的所有應用程式都會受影響。
然後抱著實驗精神,和 2.2 節一樣,我也做了幾個測試,看看 Authenticator
類對
HTTPS 的支援情況,發現無論是帶認證的 HTTPS 站點,還是通過帶認證的代理去訪問 HTTPS 站點,都沒問題。不過在測試的過程中還是發現了一些有趣的現象,在使用正確的使用者名稱和密碼時都可以成功認證,但是在使用錯誤的使用者名稱和密碼時,不同情況下的錯誤情形略有不同。有些情況可能報 407 錯誤,有些情況可能報 401 錯誤,有些情況還可能報 “java.net.ProtocolException: Server redirected too many times (20)”。雖然這可能並沒有什麼卵用,我還是在這裡記錄一下吧,算是做個總結。
注:上圖是使用 Lucidchart 線上畫的,強大的線上版 Visio ,推薦!
2.4 使用 HttpClient 的 CredentialsProvider
和 Authenticator
類似,HttpClient
也提供了一個類 CredentialsProvider
來實現
HTTP 的身份認證,它的子類BasicCredentialsProvider
用於基本身份認證。和 Authenticator
不一樣的是,這種方法不再是全域性的,而是針對指定的
HttpClient 例項有效,可以根據需要來設定。這裡不再多述,示例程式碼如下:
1
2
3
4
相關推薦Java 和 HTTP 的那些事(三) 代理認證前面一篇部落格介紹了在 Java 中使用 HttpURLConnection 和 HttpClient 通過代理訪問 HTTP 站點的方法,但是可以看到程式碼中使用的代理都是免費公開的代理,不需要使用者名稱密碼就能直接訪問。由於網際網路上公開的代理安全性不能保證,這種代 聊聊高併發(三十五)Java記憶體模型那些事(三)理解記憶體屏障硬體層提供了一系列的記憶體屏障 memory barrier / memory fence(Intel的提法)來提供一致性的能力。拿X86平臺來說,有幾種主要的記憶體屏障 1. lfence,是一種Load Barrier 讀屏障 2. sfence, 是一種Store 關於JAVA你必須知道的那些事(三):繼承和訪問修飾符今天乘著還有一些時間,把上次拖欠的面向物件程式設計三大特性中遺留的繼承和多型給簡單說明一下。這一部分還是非常重要的,需要仔細思考。 繼承 繼承:它是一種類與類之間的關係,通過使用已存在的類作為基礎來建立新類。其中已存在的類稱為父類(或基類); 建立的新類稱為子類(或派生類)。簡單的就是子類繼 聊聊高併發(三十三)Java記憶體模型那些事(一)從一致性(Consistency)的角度理解Java記憶體模型可以說併發系統要解決的最核心問題之一就是一致性的問題,關於一致性的研究已經有幾十年了,有大量的理論,演算法支援。這篇說說一致性這個主題一些經常提到的概念,理清Java記憶體模型在其中的位置。 一致性問題更準確的說是一致性需求,看系統需要什麼樣的一致性保證。比如分散式領域 多執行緒那些事(三)這篇文章主要是用簡單的程式碼展示自己在梳理和複習多執行緒的過程中的一些問題,歡迎大家指正; 關鍵字volatile /** * @program: demo * @description: ${description} * @author: Will Wu * @create: 2 Android Studio使用的那些事(三)AS不同版本安裝注意點繼上篇遷移整理了 Android Studio使用的那些事(二)AS常見錯誤 這篇將整理記錄Android Studio我在使用更新不同版本時候所遇到要注意的點。Android Studio當時的1.2、1.5 我就沒有記錄了,因為當時AS還不夠成熟問題還很多,雖然下載安裝了 線性代數的那些事(三)特徵值與正交變換嗯哼哼說下變換為什麼要進行變換因為變換後,在新空間下運算變得簡單,或者說 在變化下之前複雜難以觀察的規律變得容易觀察了其實變換的實質就是旋轉與拉伸(圖片來自:https://www.zhihu.com/question/20501504/answer/174887899)比如 工作那些事(三)什麼樣的公司能吸引你,什麼樣的公司適合你?什麼樣的公司能吸引你? 換句話說,在眾多給你offer的公司中,影響你決定,讓你徘徊和猶豫的有那些因素呢?關於這些談談我自己的想法,歡迎大家來點評。 在眾 關於JAVA你必須知道的那些事(四):單例模式和多型好吧,今天一定要把面向物件的最後一個特性:多型,給說完。不過我們先來聊一聊設計模式,因為它很重要。 設計模式 官方的解釋是,設計模式是:一套被反覆使用,多數人知曉的,經過分類編目,程式碼設計經驗的總結。說人話就是:軟體開發人員在軟體開發過程中面臨的一般問題的解決方案。 常見的設計模式可以 java開發中的那些事(1)-------關於ORA00604和ORA12705******************************有關myEclipse和oracle連線中出現的一個問題********************************* 介面拿給使用者提意見,然後又是修修改改,總算是審批通過,下一步就要開始編碼了,結果一出手 Java並發程序設計(三) Java內存模型和線程安全-h static tar -a 順序 語義 ret public font Java內存模型和線程安全 一 、原子性 原子性是指一個操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其它線程幹擾。 思考:i++是原子操作嗎? 二、有序性 我和 WebSocket 的那些事(一)com lis 都沒有 情況下 系統 並不是 任務管理 js實現 因此 我的策劃大佬離職了,在他go之前我都沒有解決好一個問題,感覺如果我換了工作面試的時候,別人問到 “你在做項目的時候,遇到的最頭疼的問題是什麽,是怎麽解決的?”,首先想到的應該也是他,今天感覺是時候寫 工作那些事(十一)談談碼農與農民工區別和發展之路 工作那些事(十二)如果哪一天,沒有了電腦 工作那些事(十三)再次失業工作那些事系列連結快速通道,不斷更新中: 工作那些事(一)今年工作不好找 工作那些事(二)應聘時填寫個人資訊ABCD 工作那些事(三)什麼樣的公司能吸引你,什麼樣的公司適合你? 工作那些事(四)大公司VS小公司 工作那些事(五)談談專案資料整理和積累 工作那些事(六)談談 關於JAVA你必須知道的那些事(二):封裝時隔近一年,我突然想起來這個文章還沒有發完,所以就繼續開始寫。也不知道自己上次寫到哪裡了,不管了這裡從面向物件的三個特性說起。 類和物件 在這之前,我們先了解什麼是物件,已經什麼是面向物件?物件:萬物皆物件,現實中實際存在的事物都可以看成一個物件。而面向物件就是人在關注物件, 關注事物的資訊 《瘋狂Java講義》學習筆記(三)資料型別和運算子1、註釋 Java語言的註釋一共有三種類型 單行註釋:用雙斜線 ”//” 表示 多行註釋:用 /*------------------*/ 表示 文件註釋:用 /**-----------------*/ 表示 如果編寫Java原始碼時添加了合適的文件註釋,然後通過JDK提供的jav http你不得不知道的那些事(六)--請求響應細節http相關的東西也寫了好幾篇了,但是一直都在涉及http周邊的東西,最核心最底層的沒有涉及到。本篇就要揭開網路請求的神祕面紗,將最底層的東西以最簡單的方式呈現給大家。 那就得先講講OSI七層模型,OSI(Open System Interconnect),即 http你不得不知道的那些事(一)--同源策略(1)前段時間詳細的學習了一下http相關的東西,特別是看了http權威指南,感覺收穫良多,在未來的一段時間我將把自己所學到的相關東西分享出來,先撿重要的(我自認為的)來說。本章講述同源策略,希望對大家有所 【HTTP】Fiddler(三)- Fiddler命令列和HTTP斷點除錯一. Fiddler內建命令。 上一節(使用Fiddler進行抓包分析)中,介紹到,在web session(與我們通常所說的session不是同一個概念,這裡的每條HTTP請求都成為一個session)介面中可以看到Fiddler抓取的所有HTTP請求.而為了更加方便的管理所有的session, Fidd Java實習面試的那些事(一)今年暑假之前,大概是六月份初,我和同班一個男生一起投簡歷開始找實習工作,因為我們倆都是主要走JavaEE方向,除了學校教了一點java基礎知識,以及jsp的知識,其他部分靠自學,當時我們班上大部分同學 [Java 多執行緒技術](三)執行緒的建立和啟動本篇部落格根據《瘋狂java講義》和《java多執行緒程式設計核心技術》整理的筆記 Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或子類的例項,每個執行緒的作用是完成一定的任務,實際上就是執行一段程式流(一段順序執行的 |