1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.client;
20
21 import java.net.URI;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28 import org.eclipse.jetty.client.api.Authentication;
29 import org.eclipse.jetty.client.api.ContentResponse;
30 import org.eclipse.jetty.client.api.Request;
31 import org.eclipse.jetty.client.api.Response;
32 import org.eclipse.jetty.client.api.Result;
33 import org.eclipse.jetty.client.util.BufferingResponseListener;
34 import org.eclipse.jetty.http.HttpHeader;
35 import org.eclipse.jetty.util.log.Log;
36 import org.eclipse.jetty.util.log.Logger;
37
38 public class AuthenticationProtocolHandler implements ProtocolHandler
39 {
40 public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
41 private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\".*", Pattern.CASE_INSENSITIVE);
42
43 private final HttpClient client;
44 private final int maxContentLength;
45 private final ResponseNotifier notifier;
46
47 public AuthenticationProtocolHandler(HttpClient client)
48 {
49 this(client, 4096);
50 }
51
52 public AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
53 {
54 this.client = client;
55 this.maxContentLength = maxContentLength;
56 this.notifier = new ResponseNotifier(client);
57 }
58
59 @Override
60 public boolean accept(Request request, Response response)
61 {
62 return response.getStatus() == 401;
63 }
64
65 @Override
66 public Response.Listener getResponseListener()
67 {
68
69 return new AuthenticationListener();
70 }
71
72 private class AuthenticationListener extends BufferingResponseListener
73 {
74 private AuthenticationListener()
75 {
76 super(maxContentLength);
77 }
78
79 @Override
80 public void onComplete(Result result)
81 {
82 Request request = result.getRequest();
83 ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
84 if (result.isFailed())
85 {
86 Throwable failure = result.getFailure();
87 LOG.debug("Authentication challenge failed {}", failure);
88 forwardFailureComplete(request, result.getRequestFailure(), response, result.getResponseFailure());
89 return;
90 }
91
92 List<WWWAuthenticate> wwwAuthenticates = parseWWWAuthenticate(response);
93 if (wwwAuthenticates.isEmpty())
94 {
95 LOG.debug("Authentication challenge without WWW-Authenticate header");
96 forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
97 return;
98 }
99
100 final URI uri = request.getURI();
101 Authentication authentication = null;
102 WWWAuthenticate wwwAuthenticate = null;
103 for (WWWAuthenticate wwwAuthn : wwwAuthenticates)
104 {
105 authentication = client.getAuthenticationStore().findAuthentication(wwwAuthn.type, uri, wwwAuthn.realm);
106 if (authentication != null)
107 {
108 wwwAuthenticate = wwwAuthn;
109 break;
110 }
111 }
112 if (authentication == null)
113 {
114 LOG.debug("No authentication available for {}", request);
115 forwardSuccessComplete(request, response);
116 return;
117 }
118
119 HttpConversation conversation = client.getConversation(request.getConversationID(), false);
120 final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
121 LOG.debug("Authentication result {}", authnResult);
122 if (authnResult == null)
123 {
124 forwardSuccessComplete(request, response);
125 return;
126 }
127
128 Request newRequest = client.copyRequest(request, uri);
129 authnResult.apply(newRequest);
130 newRequest.onResponseSuccess(new Response.SuccessListener()
131 {
132 @Override
133 public void onSuccess(Response response)
134 {
135 client.getAuthenticationStore().addAuthenticationResult(authnResult);
136 }
137 }).send(null);
138 }
139
140 private void forwardSuccessComplete(Request request, Response response)
141 {
142 HttpConversation conversation = client.getConversation(request.getConversationID(), false);
143 conversation.updateResponseListeners(null);
144 notifier.forwardSuccessComplete(conversation.getResponseListeners(), request, response);
145 }
146
147 private void forwardFailureComplete(Request request, Throwable requestFailure, Response response, Throwable responseFailure)
148 {
149 HttpConversation conversation = client.getConversation(request.getConversationID(), false);
150 conversation.updateResponseListeners(null);
151 notifier.forwardFailureComplete(conversation.getResponseListeners(), request, requestFailure, response, responseFailure);
152 }
153
154 private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
155 {
156
157 List<WWWAuthenticate> result = new ArrayList<>();
158 List<String> values = Collections.list(response.getHeaders().getValues(HttpHeader.WWW_AUTHENTICATE.asString()));
159 for (String value : values)
160 {
161 Matcher matcher = WWW_AUTHENTICATE_PATTERN.matcher(value);
162 if (matcher.matches())
163 {
164 String type = matcher.group(1);
165 String realm = matcher.group(2);
166 WWWAuthenticate wwwAuthenticate = new WWWAuthenticate(value, type, realm);
167 result.add(wwwAuthenticate);
168 }
169 }
170 return result;
171 }
172 }
173
174 private class WWWAuthenticate
175 {
176 private final String value;
177 private final String type;
178 private final String realm;
179
180 public WWWAuthenticate(String value, String type, String realm)
181 {
182 this.value = value;
183 this.type = type;
184 this.realm = realm;
185 }
186 }
187 }