1
2
3
4
5
6
7 package waffle.shiro.negotiate;
8
9 import java.io.IOException;
10 import java.util.ArrayList;
11 import java.util.Base64;
12 import java.util.List;
13 import java.util.Locale;
14
15 import javax.servlet.ServletRequest;
16 import javax.servlet.ServletResponse;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19
20 import org.apache.shiro.authc.AuthenticationException;
21 import org.apache.shiro.authc.AuthenticationToken;
22 import org.apache.shiro.subject.Subject;
23 import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
24 import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
25 import org.apache.shiro.web.util.WebUtils;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import waffle.util.AuthorizationHeader;
30 import waffle.util.NtlmServletRequest;
31
32
33
34
35
36
37
38
39
40
41 public class NegotiateAuthenticationFilter extends AuthenticatingFilter {
42
43
44
45
46 private static final Logger LOGGER = LoggerFactory.getLogger(NegotiateAuthenticationFilter.class);
47
48
49
50
51
52
53 private static final List<String> PROTOCOLS = new ArrayList<>();
54
55
56 private String failureKeyAttribute = FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;
57
58
59 private String rememberMeParam = FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM;
60
61
62
63
64 public NegotiateAuthenticationFilter() {
65 NegotiateAuthenticationFilter.PROTOCOLS.add("Negotiate");
66 NegotiateAuthenticationFilter.PROTOCOLS.add("NTLM");
67 }
68
69
70
71
72
73
74 public String getRememberMeParam() {
75 return this.rememberMeParam;
76 }
77
78
79
80
81
82
83
84
85
86
87
88
89 public void setRememberMeParam(final String value) {
90 this.rememberMeParam = value;
91 }
92
93 @Override
94 protected boolean isRememberMe(final ServletRequest request) {
95 return WebUtils.isTrue(request, this.getRememberMeParam());
96 }
97
98 @Override
99 protected AuthenticationToken createToken(final ServletRequest request, final ServletResponse response) {
100 final String authorization = this.getAuthzHeader(request);
101 final String[] elements = authorization.split(" ", -1);
102 final byte[] inToken = Base64.getDecoder().decode(elements[1]);
103
104
105
106 final String connectionId = NtlmServletRequest.getConnectionId((HttpServletRequest) request);
107 final String securityPackage = elements[0];
108
109
110 final AuthorizationHeader authorizationHeader = new AuthorizationHeader((HttpServletRequest) request);
111 final boolean ntlmPost = authorizationHeader.isNtlmType1PostAuthorizationHeader();
112
113 NegotiateAuthenticationFilter.LOGGER.debug("security package: {}, connection id: {}, ntlmPost: {}",
114 securityPackage, connectionId, Boolean.valueOf(ntlmPost));
115
116 final boolean rememberMe = this.isRememberMe(request);
117 final String host = this.getHost(request);
118
119 return new NegotiateToken(inToken, new byte[0], connectionId, securityPackage, ntlmPost, rememberMe, host);
120 }
121
122 @Override
123 protected boolean onLoginSuccess(final AuthenticationToken token, final Subject subject,
124 final ServletRequest request, final ServletResponse response) throws Exception {
125 request.setAttribute("MY_SUBJECT", ((NegotiateToken) token).getSubject());
126 return true;
127 }
128
129 @Override
130 protected boolean onLoginFailure(final AuthenticationToken token, final AuthenticationException e,
131 final ServletRequest request, final ServletResponse response) {
132 if (e instanceof AuthenticationInProgressException) {
133
134 final String protocol = this.getAuthzHeaderProtocol(request);
135 NegotiateAuthenticationFilter.LOGGER.debug("Negotiation in progress for protocol: {}", protocol);
136 this.sendChallengeDuringNegotiate(protocol, response, ((NegotiateToken) token).getOut());
137 return false;
138 }
139 NegotiateAuthenticationFilter.LOGGER.warn("login exception: {}", e.getMessage());
140
141
142 this.sendChallengeOnFailure(response);
143
144 this.setFailureAttribute(request, e);
145 return true;
146 }
147
148
149
150
151
152
153
154
155
156 protected void setFailureAttribute(final ServletRequest request, final AuthenticationException ae) {
157 final String className = ae.getClass().getName();
158 request.setAttribute(this.getFailureKeyAttribute(), className);
159 }
160
161
162
163
164
165
166 public String getFailureKeyAttribute() {
167 return this.failureKeyAttribute;
168 }
169
170
171
172
173
174
175
176 public void setFailureKeyAttribute(final String value) {
177 this.failureKeyAttribute = value;
178 }
179
180 @Override
181 protected boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws Exception {
182
183 boolean loggedIn = false;
184
185 if (this.isLoginAttempt(request)) {
186 loggedIn = this.executeLogin(request, response);
187 } else {
188 NegotiateAuthenticationFilter.LOGGER.debug("authorization required, supported protocols: {}",
189 NegotiateAuthenticationFilter.PROTOCOLS);
190 this.sendChallengeInitiateNegotiate(response);
191 }
192 return loggedIn;
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 private String getAuthzHeader(final ServletRequest request) {
210 final HttpServletRequest httpRequest = WebUtils.toHttp(request);
211 return httpRequest.getHeader("Authorization");
212 }
213
214
215
216
217
218
219
220
221
222 private String getAuthzHeaderProtocol(final ServletRequest request) {
223 final String authzHeader = this.getAuthzHeader(request);
224 return authzHeader.substring(0, authzHeader.indexOf(' '));
225 }
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241 private boolean isLoginAttempt(final ServletRequest request) {
242 final String authzHeader = this.getAuthzHeader(request);
243 return authzHeader != null && this.isLoginAttempt(authzHeader);
244 }
245
246
247
248
249
250
251
252
253
254
255
256
257 boolean isLoginAttempt(final String authzHeader) {
258 for (final String protocol : NegotiateAuthenticationFilter.PROTOCOLS) {
259 if (authzHeader.toLowerCase(Locale.ENGLISH).startsWith(protocol.toLowerCase(Locale.ENGLISH))) {
260 return true;
261 }
262 }
263 return false;
264 }
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 private void sendChallenge(final List<String> protocols, final ServletResponse response, final byte[] out) {
280 final HttpServletResponse httpResponse = WebUtils.toHttp(response);
281 this.sendAuthenticateHeader(protocols, out, httpResponse);
282 httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
283 }
284
285
286
287
288
289
290
291 void sendChallengeInitiateNegotiate(final ServletResponse response) {
292 this.sendChallenge(NegotiateAuthenticationFilter.PROTOCOLS, response, null);
293 }
294
295
296
297
298
299
300
301
302
303
304
305 void sendChallengeDuringNegotiate(final String protocol, final ServletResponse response, final byte[] out) {
306 final List<String> protocolsList = new ArrayList<>();
307 protocolsList.add(protocol);
308 this.sendChallenge(protocolsList, response, out);
309 }
310
311
312
313
314
315
316
317 void sendChallengeOnFailure(final ServletResponse response) {
318 final HttpServletResponse httpResponse = WebUtils.toHttp(response);
319 this.sendUnauthorized(NegotiateAuthenticationFilter.PROTOCOLS, null, httpResponse);
320 httpResponse.setHeader("Connection", "close");
321 try {
322 httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
323 httpResponse.flushBuffer();
324 } catch (final IOException e) {
325 throw new RuntimeException(e);
326 }
327 }
328
329
330
331
332
333
334
335
336
337
338
339 private void sendAuthenticateHeader(final List<String> protocolsList, final byte[] out,
340 final HttpServletResponse httpResponse) {
341 this.sendUnauthorized(protocolsList, out, httpResponse);
342 httpResponse.setHeader("Connection", "keep-alive");
343 }
344
345
346
347
348
349
350
351
352
353
354
355 private void sendUnauthorized(final List<String> protocols, final byte[] out, final HttpServletResponse response) {
356 for (final String protocol : protocols) {
357 if (out == null || out.length == 0) {
358 response.addHeader("WWW-Authenticate", protocol);
359 } else {
360 response.setHeader("WWW-Authenticate", protocol + " " + Base64.getEncoder().encodeToString(out));
361 }
362 }
363 }
364
365 }