Digest is now listening to correct headers
This commit is contained in:
parent
c4efab0609
commit
2c7a9a6eff
5 changed files with 107 additions and 29 deletions
|
|
@ -195,7 +195,7 @@ public class StringUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static ArrayList<String> SPACES = new ArrayList<String>();
|
private static ArrayList<String> SPACES = new ArrayList<>();
|
||||||
/**
|
/**
|
||||||
* @return A string containing a specific amount of spaces
|
* @return A string containing a specific amount of spaces
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -148,19 +148,22 @@ public class HttpHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void setIsRequest(boolean request) { this.request = request; }
|
public void setIsRequest(boolean request) { this.request = request; }
|
||||||
protected void setRequestType(String type){
|
public void setRequestType(String type){
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
protected void setHTTPVersion(float version){
|
public void setHTTPVersion(float version){
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
protected void setHTTPCode(int code){
|
public void setHTTPCode(int code){
|
||||||
this.httpCode = code;
|
this.httpCode = code;
|
||||||
}
|
}
|
||||||
protected void setRequestURL(String url){
|
public void setRequestURL(String url){
|
||||||
this.url = url.trim().replaceAll("//", "/");
|
this.url = url.trim().replaceAll("//", "/");
|
||||||
}
|
}
|
||||||
|
public void setHeader(String key, String value){
|
||||||
|
this.headers.put(key.toUpperCase(), value);
|
||||||
|
}
|
||||||
protected void setInputStream(InputStream in){
|
protected void setInputStream(InputStream in){
|
||||||
this.in = in;
|
this.in = in;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ public class HttpHeaderParser {
|
||||||
* stores them in a HashMap. If a pair only contain a key the the value
|
* stores them in a HashMap. If a pair only contain a key the the value
|
||||||
* will be set as a empty string.
|
* will be set as a empty string.
|
||||||
*
|
*
|
||||||
|
* TODO: method is not quote aware
|
||||||
* @param map the Map where key and values will be stored.
|
* @param map the Map where key and values will be stored.
|
||||||
* @param headerValue the raw header value String that will be parsed.
|
* @param headerValue the raw header value String that will be parsed.
|
||||||
* @param delimiter the delimiter that separates key and value pairs (e.g. ';' for Cookies or ',' for Cache-Control)
|
* @param delimiter the delimiter that separates key and value pairs (e.g. ';' for Cookies or ',' for Cache-Control)
|
||||||
|
|
@ -168,8 +169,8 @@ public class HttpHeaderParser {
|
||||||
for(String cookie : tmpArr){
|
for(String cookie : tmpArr){
|
||||||
String[] tmpStr = PATTERN_EQUAL.split(cookie, 2);
|
String[] tmpStr = PATTERN_EQUAL.split(cookie, 2);
|
||||||
map.put(
|
map.put(
|
||||||
tmpStr[0].trim(), // Key
|
tmpStr[0].trim(), // Key
|
||||||
StringUtil.trim((tmpStr.length>1 ? tmpStr[1] : "").trim(), '\"')); //Value
|
StringUtil.trim((tmpStr.length>1 ? tmpStr[1] : "").trim(), '\"')); //Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,45 @@
|
||||||
package zutil.net.http.page;
|
package zutil.net.http.page;
|
||||||
|
|
||||||
|
import zutil.Encrypter;
|
||||||
|
import zutil.Hasher;
|
||||||
import zutil.net.http.HttpHeader;
|
import zutil.net.http.HttpHeader;
|
||||||
import zutil.net.http.HttpPage;
|
import zutil.net.http.HttpPage;
|
||||||
import zutil.net.http.HttpPrintStream;
|
import zutil.net.http.HttpPrintStream;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A abstract page that requires HTTP Digest authentication
|
* A abstract page that requires HTTP Digest authentication
|
||||||
* to access the subclass HttpPage.
|
* to access the subclass HttpPage.
|
||||||
*
|
*
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc2617">rfc2617</a>
|
* @see <a href="https://tools.ietf.org/html/rfc2069">rfc2069</a>
|
||||||
* @author Ziver
|
* @author Ziver
|
||||||
*/
|
*/
|
||||||
public abstract class HttpDigestAuthPage implements HttpPage{
|
public abstract class HttpDigestAuthPage implements HttpPage{
|
||||||
|
private static final String DEAFULT_REALM = "Login";
|
||||||
private static final String HTTP_AUTH_HEADER = "WWW-Authenticate";
|
private static final String HTTP_AUTH_HEADER = "WWW-Authenticate";
|
||||||
|
private static final String HTTP_CLIENT_HEADER = "Authorization";
|
||||||
private static final String AUTH_TYPE = "Digest";
|
private static final String AUTH_TYPE = "Digest";
|
||||||
|
|
||||||
private static final String AUTH_REALM = "realm";
|
private static final String AUTH_REALM = "realm";
|
||||||
private static final String AUTH_QUALITY_OF_PROTECTION = "qop";
|
|
||||||
private static final String AUTH_NONCE = "nonce";
|
private static final String AUTH_NONCE = "nonce";
|
||||||
private static final String AUTH_OPAQUE = "opaque";
|
private static final String AUTH_OPAQUE = "opaque"; // OPTIONAL can be used as session data
|
||||||
|
|
||||||
private static final String AUTH_USERNAME = "username";
|
private static final String AUTH_USERNAME = "username";
|
||||||
private static final String AUTH_URI = "uri";
|
private static final String AUTH_URI = "uri";
|
||||||
private static final String AUTH_CNONCE = "cnonce";
|
|
||||||
private static final String AUTH_RESPONSE = "response";
|
private static final String AUTH_RESPONSE = "response";
|
||||||
|
|
||||||
|
|
||||||
private String realm;
|
private String realm = DEAFULT_REALM;
|
||||||
|
private SecureRandom secRandom = new SecureRandom();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void setRealm(String realm){
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -39,20 +49,61 @@ public abstract class HttpDigestAuthPage implements HttpPage{
|
||||||
Map<String, String> cookie,
|
Map<String, String> cookie,
|
||||||
Map<String, String> request) throws IOException {
|
Map<String, String> request) throws IOException {
|
||||||
|
|
||||||
out.setStatusCode(401);
|
if (headers.getHeader(HTTP_CLIENT_HEADER) == null) {
|
||||||
out.setHeader(HTTP_AUTH_HEADER, generateAuthHeader());
|
session.put(AUTH_NONCE, generateNonce());
|
||||||
|
out.setStatusCode(401);
|
||||||
|
out.setHeader(HTTP_AUTH_HEADER, generateAuthHeader((String) session.get(AUTH_NONCE)));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
authRespond(out, headers, session, cookie, request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateAuthHeader(){
|
|
||||||
|
private String generateAuthHeader(String nonce){
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
str.append(AUTH_TYPE).append(' ');
|
str.append(AUTH_TYPE).append(' ');
|
||||||
str.append(AUTH_REALM).append("=\"").append("ll").append("\", ");
|
str.append(AUTH_REALM).append("=\"").append(realm).append("\", ");
|
||||||
str.append(AUTH_QUALITY_OF_PROTECTION).append("=\"").append("ll").append("\", ");
|
str.append(AUTH_NONCE).append("=\"").append(nonce).append("\", ");
|
||||||
str.append(AUTH_NONCE).append("=\"").append("ll").append("\", ");
|
//str.append(AUTH_OPAQUE).append("=\"").append("ll").append("\"");
|
||||||
str.append(AUTH_OPAQUE).append("=\"").append("ll").append("\"");
|
|
||||||
return str.toString();
|
return str.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String generateNonce(){
|
||||||
|
byte[] buff = new byte[128/8];
|
||||||
|
secRandom.nextBytes(buff);
|
||||||
|
return Hasher.SHA1(buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateH1(String username, String password, String realm) {
|
||||||
|
String ha1 = null;
|
||||||
|
// If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
||||||
|
// HA1=MD5(username:realm:password)
|
||||||
|
ha1 = Hasher.MD5(username +":"+ realm +":"+ password);
|
||||||
|
// If the algorithm directive's value is "MD5-sess", then HA1 is
|
||||||
|
// HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
||||||
|
return ha1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateH2(String uri) {
|
||||||
|
String ha2;
|
||||||
|
// If the qop directive's value is "auth" or is unspecified, then HA2 is
|
||||||
|
// HA2=MD5(method:digestURI)
|
||||||
|
ha2 = Hasher.MD5("MD5:"+ uri);
|
||||||
|
// If the qop directive's value is "auth-int", then HA2 is
|
||||||
|
// HA2=MD5(method:digestURI:MD5(entityBody))
|
||||||
|
return ha2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateResponseHash(String ha1, String ha2, String nonce){
|
||||||
|
String response;
|
||||||
|
// If the qop directive's value is "auth" or "auth-int", then compute the response as follows:
|
||||||
|
// response=MD5(HA1:nonce:nonceCount:cnonce:qop:HA2)
|
||||||
|
// If the qop directive is unspecified, then compute the response as follows:
|
||||||
|
// response=MD5(HA1:nonce:HA2)
|
||||||
|
response = Hasher.MD5(ha1 +":"+ nonce +":"+ ha2);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract void authRespond(HttpPrintStream out,
|
public abstract void authRespond(HttpPrintStream out,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package zutil.net.http.page;
|
package zutil.net.http.page;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import zutil.Hasher;
|
||||||
import zutil.io.StringOutputStream;
|
import zutil.io.StringOutputStream;
|
||||||
import zutil.net.http.HttpHeader;
|
import zutil.net.http.HttpHeader;
|
||||||
import zutil.net.http.HttpHeaderParser;
|
import zutil.net.http.HttpHeaderParser;
|
||||||
|
|
@ -20,20 +21,42 @@ public class HttpDigestAuthPageTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cleanRequest() throws IOException {
|
public void cleanRequest() throws IOException {
|
||||||
HttpHeader header = new HttpHeader();
|
HttpHeader rspHeader = makeRequest(new HttpHeader());
|
||||||
HttpHeader output = makeRequest(header);
|
|
||||||
|
|
||||||
assertEquals(401, output.getHTTPCode());
|
assertEquals(401, rspHeader.getHTTPCode());
|
||||||
assertTrue(output.getHeader("WWW-Authenticate") != null);
|
assertTrue(rspHeader.getHeader("WWW-Authenticate") != null);
|
||||||
assertEquals("Digest", parseAuthType(output));
|
assertEquals("Digest", parseAuthType(rspHeader));
|
||||||
Map<String,String> authHeader = parseAuthHeader(output);
|
Map<String,String> authHeader = parseAuthHeader(rspHeader);
|
||||||
assertTrue(authHeader.containsKey("realm"));
|
assertTrue(authHeader.containsKey("realm"));
|
||||||
assertTrue(authHeader.containsKey("qop"));
|
|
||||||
assertTrue(authHeader.containsKey("nonce"));
|
assertTrue(authHeader.containsKey("nonce"));
|
||||||
assertTrue(authHeader.containsKey("opaque"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticate() throws IOException {
|
||||||
|
HttpHeader reqHeader = new HttpHeader();
|
||||||
|
HttpHeader rspHeader = makeRequest(reqHeader);
|
||||||
|
Map<String,String> authHeader = parseAuthHeader(rspHeader);
|
||||||
|
reqHeader = new HttpHeader();
|
||||||
|
|
||||||
|
String realm = authHeader.get("realm");
|
||||||
|
String nonce = authHeader.get("nonce");
|
||||||
|
String uri = "/login";
|
||||||
|
|
||||||
|
String ha1 = Hasher.MD5("username:password");
|
||||||
|
String ha2 = Hasher.MD5("MD5:/" +uri);
|
||||||
|
String response = Hasher.MD5(ha1 +":"+ nonce +":"+ ha2);
|
||||||
|
reqHeader.setHeader("Authorization", "Digest username=\"username\", " +
|
||||||
|
"realm=\""+realm+"\", " +
|
||||||
|
"nonce=\""+nonce+"\", " +
|
||||||
|
"uri=\""+uri+"\", " +
|
||||||
|
"response=\""+response+"\"");
|
||||||
|
rspHeader = makeRequest(reqHeader);
|
||||||
|
assertEquals(200, rspHeader.getHTTPCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue