1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.catalina.authenticator;
18
19 import java.io.IOException;
20 import java.security.Principal;
21 import java.security.cert.X509Certificate;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Set;
26
27 import javax.security.auth.Subject;
28 import javax.security.auth.callback.CallbackHandler;
29 import javax.security.auth.message.AuthException;
30 import javax.security.auth.message.AuthStatus;
31 import javax.security.auth.message.MessageInfo;
32 import javax.security.auth.message.config.AuthConfigFactory;
33 import javax.security.auth.message.config.AuthConfigProvider;
34 import javax.security.auth.message.config.RegistrationListener;
35 import javax.security.auth.message.config.ServerAuthConfig;
36 import javax.security.auth.message.config.ServerAuthContext;
37 import javax.servlet.DispatcherType;
38 import javax.servlet.ServletContext;
39 import javax.servlet.ServletException;
40 import javax.servlet.http.Cookie;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43
44 import org.apache.catalina.Authenticator;
45 import org.apache.catalina.Container;
46 import org.apache.catalina.Context;
47 import org.apache.catalina.Globals;
48 import org.apache.catalina.LifecycleException;
49 import org.apache.catalina.Realm;
50 import org.apache.catalina.Session;
51 import org.apache.catalina.TomcatPrincipal;
52 import org.apache.catalina.Valve;
53 import org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl;
54 import org.apache.catalina.authenticator.jaspic.MessageInfoImpl;
55 import org.apache.catalina.connector.Request;
56 import org.apache.catalina.connector.Response;
57 import org.apache.catalina.filters.CorsFilter;
58 import org.apache.catalina.filters.RemoteIpFilter;
59 import org.apache.catalina.realm.GenericPrincipal;
60 import org.apache.catalina.util.SessionIdGeneratorBase;
61 import org.apache.catalina.util.StandardSessionIdGenerator;
62 import org.apache.catalina.valves.RemoteIpValve;
63 import org.apache.catalina.valves.ValveBase;
64 import org.apache.coyote.ActionCode;
65 import org.apache.juli.logging.Log;
66 import org.apache.juli.logging.LogFactory;
67 import org.apache.tomcat.util.ExceptionUtils;
68 import org.apache.tomcat.util.descriptor.web.FilterDef;
69 import org.apache.tomcat.util.descriptor.web.FilterMap;
70 import org.apache.tomcat.util.descriptor.web.LoginConfig;
71 import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
72 import org.apache.tomcat.util.http.FastHttpDateFormat;
73 import org.apache.tomcat.util.http.RequestUtil;
74 import org.apache.tomcat.util.res.StringManager;
75
76 /**
77 * Basic implementation of the <b>Valve</b> interface that enforces the
78 * <code><security-constraint></code> elements in the web application
79 * deployment descriptor. This functionality is implemented as a Valve so that
80 * it can be omitted in environments that do not require these features.
81 * Individual implementations of each supported authentication method can
82 * subclass this base class as required.
83 * <p>
84 * <b>USAGE CONSTRAINT</b>: When this class is utilized, the Context to which it
85 * is attached (or a parent Container in a hierarchy) must have an associated
86 * Realm that can be used for authenticating users and enumerating the roles to
87 * which they have been assigned.
88 * <p>
89 * <b>USAGE CONSTRAINT</b>: This Valve is only useful when processing HTTP
90 * requests. Requests of any other type will simply be passed through.
91 *
92 * @author Craig R. McClanahan
93 */
94 public abstract class AuthenticatorBase extends ValveBase
95 implements Authenticator, RegistrationListener {
96
97 private final Log log = LogFactory.getLog(AuthenticatorBase.class); // must not be static
98
99 /**
100 * "Expires" header always set to Date(1), so generate once only
101 */
102 private static final String DATE_ONE = FastHttpDateFormat.formatDate(1);
103
104 /**
105 * The string manager for this package.
106 */
107 protected static final StringManager sm = StringManager.getManager(AuthenticatorBase.class);
108
109 /**
110 * Authentication header
111 */
112 protected static final String AUTH_HEADER_NAME = "WWW-Authenticate";
113
114 /**
115 * Default authentication realm name.
116 */
117 protected static final String REALM_NAME = "Authentication required";
118
119 protected static String getRealmName(Context context) {
120 if (context == null) {
121 // Very unlikely
122 return REALM_NAME;
123 }
124
125 LoginConfig config = context.getLoginConfig();
126 if (config == null) {
127 return REALM_NAME;
128 }
129
130 String result = config.getRealmName();
131 if (result == null) {
132 return REALM_NAME;
133 }
134
135 return result;
136 }
137
138 // ------------------------------------------------------ Constructor
139
140 public AuthenticatorBase() {
141 super(true);
142 }
143
144 // ----------------------------------------------------- Instance Variables
145
146 /**
147 * Should a session always be used once a user is authenticated? This may
148 * offer some performance benefits since the session can then be used to
149 * cache the authenticated Principal, hence removing the need to
150 * authenticate the user via the Realm on every request. This may be of help
151 * for combinations such as BASIC authentication used with the JNDIRealm or
152 * DataSourceRealms. However there will also be the performance cost of
153 * creating and GC'ing the session. By default, a session will not be
154 * created.
155 */
156 protected boolean alwaysUseSession = false;
157
158 /**
159 * Should we cache authenticated Principals if the request is part of an
160 * HTTP session?
161 */
162 protected boolean cache = true;
163
164 /**
165 * Should the session ID, if any, be changed upon a successful
166 * authentication to prevent a session fixation attack?
167 */
168 protected boolean changeSessionIdOnAuthentication = true;
169
170 /**
171 * The Context to which this Valve is attached.
172 */
173 protected Context context = null;
174
175 /**
176 * Flag to determine if we disable proxy caching, or leave the issue up to
177 * the webapp developer.
178 */
179 protected boolean disableProxyCaching = true;
180
181 /**
182 * Flag to determine if we disable proxy caching with headers incompatible
183 * with IE.
184 */
185 protected boolean securePagesWithPragma = false;
186
187 /**
188 * The Java class name of the secure random number generator class to be
189 * used when generating SSO session identifiers. The random number generator
190 * class must be self-seeding and have a zero-argument constructor. If not
191 * specified, an instance of {@link java.security.SecureRandom} will be
192 * generated.
193 */
194 protected String secureRandomClass = null;
195
196 /**
197 * The name of the algorithm to use to create instances of
198 * {@link java.security.SecureRandom} which are used to generate SSO session
199 * IDs. If no algorithm is specified, SHA1PRNG is used. To use the platform
200 * default (which may be SHA1PRNG), specify the empty string. If an invalid
201 * algorithm and/or provider is specified the SecureRandom instances will be
202 * created using the defaults. If that fails, the SecureRandom instances
203 * will be created using platform defaults.
204 */
205 protected String secureRandomAlgorithm = "SHA1PRNG";
206
207 /**
208 * The name of the provider to use to create instances of
209 * {@link java.security.SecureRandom} which are used to generate session SSO
210 * IDs. If no algorithm is specified the of SHA1PRNG default is used. If an
211 * invalid algorithm and/or provider is specified the SecureRandom instances
212 * will be created using the defaults. If that fails, the SecureRandom
213 * instances will be created using platform defaults.
214 */
215 protected String secureRandomProvider = null;
216
217 /**
218 * The name of the JASPIC callback handler class. If none is specified the
219 * default {@link org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl}
220 * will be used.
221 */
222 protected String jaspicCallbackHandlerClass = null;
223
224 /**
225 * Should the auth information (remote user and auth type) be returned as response
226 * headers for a forwarded/proxied request? When the {@link RemoteIpValve} or
227 * {@link RemoteIpFilter} mark a forwarded request with the
228 * {@link Globals#REQUEST_FORWARDED_ATTRIBUTE} this authenticator can return the
229 * values of {@link HttpServletRequest#getRemoteUser()} and
230 * {@link HttpServletRequest#getAuthType()} as reponse headers {@code remote-user}
231 * and {@code auth-type} to a reverse proxy. This is useful, e.g., for access log
232 * consistency or other decisions to make.
233 */
234
235 protected boolean sendAuthInfoResponseHeaders = false;
236
237 protected SessionIdGeneratorBase sessionIdGenerator = null;
238
239 /**
240 * The SingleSignOn implementation in our request processing chain, if there
241 * is one.
242 */
243 protected SingleSignOn sso = null;
244
245 private AllowCorsPreflight allowCorsPreflight = AllowCorsPreflight.NEVER;
246
247 private volatile String jaspicAppContextID = null;
248 private volatile Optional<AuthConfigProvider> jaspicProvider = null;
249
250
251 // ------------------------------------------------------------- Properties
252
253 public String getAllowCorsPreflight() {
254 return allowCorsPreflight.name().toLowerCase(Locale.ENGLISH);
255 }
256
257 public void setAllowCorsPreflight(String allowCorsPreflight) {
258 this.allowCorsPreflight = AllowCorsPreflight.valueOf(allowCorsPreflight.trim().toUpperCase(Locale.ENGLISH));
259 }
260
261 public boolean getAlwaysUseSession() {
262 return alwaysUseSession;
263 }
264
265 public void setAlwaysUseSession(boolean alwaysUseSession) {
266 this.alwaysUseSession = alwaysUseSession;
267 }
268
269 /**
270 * Return the cache authenticated Principals flag.
271 *
272 * @return <code>true</code> if authenticated Principals will be cached,
273 * otherwise <code>false</code>
274 */
275 public boolean getCache() {
276 return this.cache;
277 }
278
279 /**
280 * Set the cache authenticated Principals flag.
281 *
282 * @param cache
283 * The new cache flag
284 */
285 public void setCache(boolean cache) {
286 this.cache = cache;
287 }
288
289 /**
290 * Return the Container to which this Valve is attached.
291 */
292 @Override
293 public Container getContainer() {
294 return this.context;
295 }
296
297 /**
298 * Set the Container to which this Valve is attached.
299 *
300 * @param container
301 * The container to which we are attached
302 */
303 @Override
304 public void setContainer(Container container) {
305
306 if (container != null && !(container instanceof Context)) {
307 throw new IllegalArgumentException(sm.getString("authenticator.notContext"));
308 }
309
310 super.setContainer(container);
311 this.context = (Context) container;
312
313 }
314
315 /**
316 * Return the flag that states if we add headers to disable caching by
317 * proxies.
318 *
319 * @return <code>true</code> if the headers will be added, otherwise
320 * <code>false</code>
321 */
322 public boolean getDisableProxyCaching() {
323 return disableProxyCaching;
324 }
325
326 /**
327 * Set the value of the flag that states if we add headers to disable
328 * caching by proxies.
329 *
330 * @param nocache
331 * <code>true</code> if we add headers to disable proxy caching,
332 * <code>false</code> if we leave the headers alone.
333 */
334 public void setDisableProxyCaching(boolean nocache) {
335 disableProxyCaching = nocache;
336 }
337
338 /**
339 * Return the flag that states, if proxy caching is disabled, what headers
340 * we add to disable the caching.
341 *
342 * @return <code>true</code> if a Pragma header should be used, otherwise
343 * <code>false</code>
344 */
345 public boolean getSecurePagesWithPragma() {
346 return securePagesWithPragma;
347 }
348
349 /**
350 * Set the value of the flag that states what headers we add to disable
351 * proxy caching.
352 *
353 * @param securePagesWithPragma
354 * <code>true</code> if we add headers which are incompatible
355 * with downloading office documents in IE under SSL but which
356 * fix a caching problem in Mozilla.
357 */
358 public void setSecurePagesWithPragma(boolean securePagesWithPragma) {
359 this.securePagesWithPragma = securePagesWithPragma;
360 }
361
362 /**
363 * Return the flag that states if we should change the session ID of an
364 * existing session upon successful authentication.
365 *
366 * @return <code>true</code> to change session ID upon successful
367 * authentication, <code>false</code> to do not perform the change.
368 */
369 public boolean getChangeSessionIdOnAuthentication() {
370 return changeSessionIdOnAuthentication;
371 }
372
373 /**
374 * Set the value of the flag that states if we should change the session ID
375 * of an existing session upon successful authentication.
376 *
377 * @param changeSessionIdOnAuthentication <code>true</code> to change
378 * session ID upon successful authentication, <code>false</code>
379 * to do not perform the change.
380 */
381 public void setChangeSessionIdOnAuthentication(boolean changeSessionIdOnAuthentication) {
382 this.changeSessionIdOnAuthentication = changeSessionIdOnAuthentication;
383 }
384
385 /**
386 * Return the secure random number generator class name.
387 *
388 * @return The fully qualified name of the SecureRandom implementation to
389 * use
390 */
391 public String getSecureRandomClass() {
392 return this.secureRandomClass;
393 }
394
395 /**
396 * Set the secure random number generator class name.
397 *
398 * @param secureRandomClass
399 * The new secure random number generator class name
400 */
401 public void setSecureRandomClass(String secureRandomClass) {
402 this.secureRandomClass = secureRandomClass;
403 }
404
405 /**
406 * Return the secure random number generator algorithm name.
407 *
408 * @return The name of the SecureRandom algorithm used
409 */
410 public String getSecureRandomAlgorithm() {
411 return secureRandomAlgorithm;
412 }
413
414 /**
415 * Set the secure random number generator algorithm name.
416 *
417 * @param secureRandomAlgorithm
418 * The new secure random number generator algorithm name
419 */
420 public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
421 this.secureRandomAlgorithm = secureRandomAlgorithm;
422 }
423
424 /**
425 * Return the secure random number generator provider name.
426 *
427 * @return The name of the SecureRandom provider
428 */
429 public String getSecureRandomProvider() {
430 return secureRandomProvider;
431 }
432
433 /**
434 * Set the secure random number generator provider name.
435 *
436 * @param secureRandomProvider
437 * The new secure random number generator provider name
438 */
439 public void setSecureRandomProvider(String secureRandomProvider) {
440 this.secureRandomProvider = secureRandomProvider;
441 }
442
443 /**
444 * Return the JASPIC callback handler class name
445 *
446 * @return The name of the JASPIC callback handler
447 */
448 public String getJaspicCallbackHandlerClass() {
449 return jaspicCallbackHandlerClass;
450 }
451
452 /**
453 * Set the JASPIC callback handler class name
454 *
455 * @param jaspicCallbackHandlerClass
456 * The new JASPIC callback handler class name
457 */
458 public void setJaspicCallbackHandlerClass(String jaspicCallbackHandlerClass) {
459 this.jaspicCallbackHandlerClass = jaspicCallbackHandlerClass;
460 }
461
462 /**
463 * Returns the flag whether authentication information will be sent to a reverse
464 * proxy on a forwarded request.
465 *
466 * @return {@code true} if response headers shall be sent, {@code false} otherwise
467 */
468 public boolean isSendAuthInfoResponseHeaders() {
469 return sendAuthInfoResponseHeaders;
470 }
471
472 /**
473 * Sets the flag whether authentication information will be send to a reverse
474 * proxy on a forwarded request.
475 *
476 * @param sendAuthInfoResponseHeaders {@code true} if response headers shall be
477 * sent, {@code false} otherwise
478 */
479 public void setSendAuthInfoResponseHeaders(boolean sendAuthInfoResponseHeaders) {
480 this.sendAuthInfoResponseHeaders = sendAuthInfoResponseHeaders;
481 }
482
483 // --------------------------------------------------------- Public Methods
484
485 /**
486 * Enforce the security restrictions in the web application deployment
487 * descriptor of our associated Context.
488 *
489 * @param request
490 * Request to be processed
491 * @param response
492 * Response to be processed
493 *
494 * @exception IOException
495 * if an input/output error occurs
496 * @exception ServletException
497 * if thrown by a processing element
498 */
499 @Override
500 public void invoke(Request request, Response response) throws IOException, ServletException {
501
502 if (log.isDebugEnabled()) {
503 log.debug("Security checking request " + request.getMethod() + " " +
504 request.getRequestURI());
505 }
506
507 // Have we got a cached authenticated Principal to record?
508 if (cache) {
509 Principal principal = request.getUserPrincipal();
510 if (principal == null) {
511 Session session = request.getSessionInternal(false);
512 if (session != null) {
513 principal = session.getPrincipal();
514 if (principal != null) {
515 if (log.isDebugEnabled()) {
516 log.debug("We have cached auth type " + session.getAuthType() +
517 " for principal " + principal);
518 }
519 request.setAuthType(session.getAuthType());
520 request.setUserPrincipal(principal);
521 }
522 }
523 }
524 }
525
526 boolean authRequired = isContinuationRequired(request);
527
528 Realm realm = this.context.getRealm();
529 // Is this request URI subject to a security constraint?
530 SecurityConstraint[] constraints = realm.findSecurityConstraints(request, this.context);
531
532 AuthConfigProvider jaspicProvider = getJaspicProvider();
533 if (jaspicProvider != null) {
534 authRequired = true;
535 }
536
537 if (constraints == null && !context.getPreemptiveAuthentication() && !authRequired) {
538 if (log.isDebugEnabled()) {
539 log.debug("Not subject to any constraint");
540 }
541 getNext().invoke(request, response);
542 return;
543 }
544
545 // Make sure that constrained resources are not cached by web proxies
546 // or browsers as caching can provide a security hole
547 if (constraints != null && disableProxyCaching &&
548 !"POST".equalsIgnoreCase(request.getMethod())) {
549 if (securePagesWithPragma) {
550 // Note: These can cause problems with downloading files with IE
551 response.setHeader("Pragma", "No-cache");
552 response.setHeader("Cache-Control", "no-cache");
553 } else {
554 response.setHeader("Cache-Control", "private");
555 }
556 response.setHeader("Expires", DATE_ONE);
557 }
558
559 if (constraints != null) {
560 // Enforce any user data constraint for this security constraint
561 if (log.isDebugEnabled()) {
562 log.debug("Calling hasUserDataPermission()");
563 }
564 if (!realm.hasUserDataPermission(request, response, constraints)) {
565 if (log.isDebugEnabled()) {
566 log.debug("Failed hasUserDataPermission() test");
567 }
568 /*
569 * ASSERT: Authenticator already set the appropriate HTTP status
570 * code, so we do not have to do anything special
571 */
572 return;
573 }
574 }
575
576 // Since authenticate modifies the response on failure,
577 // we have to check for allow-from-all first.
578 boolean hasAuthConstraint = false;
579 if (constraints != null) {
580 hasAuthConstraint = true;
581 for (int i = 0; i < constraints.length && hasAuthConstraint; i++) {
582 if (!constraints[i].getAuthConstraint()) {
583 hasAuthConstraint = false;
584 } else if (!constraints[i].getAllRoles() &&
585 !constraints[i].getAuthenticatedUsers()) {
586 String[] roles = constraints[i].findAuthRoles();
587 if (roles == null || roles.length == 0) {
588 hasAuthConstraint = false;
589 }
590 }
591 }
592 }
593
594 if (!authRequired && hasAuthConstraint) {
595 authRequired = true;
596 }
597
598 if (!authRequired && context.getPreemptiveAuthentication()) {
599 authRequired =
600 request.getCoyoteRequest().getMimeHeaders().getValue("authorization") != null;
601 }
602
603 if (!authRequired && context.getPreemptiveAuthentication() &&
604 HttpServletRequest.CLIENT_CERT_AUTH.equals(getAuthMethod())) {
605 X509Certificate[] certs = getRequestCertificates(request);
606 authRequired = certs != null && certs.length > 0;
607 }
608
609 JaspicState jaspicState = null;
610
611 if ((authRequired || constraints != null) && allowCorsPreflightBypass(request)) {
612 if (log.isDebugEnabled()) {
613 log.debug("CORS Preflight request bypassing authentication");
614 }
615 getNext().invoke(request, response);
616 return;
617 }
618
619 if (authRequired) {
620 if (log.isDebugEnabled()) {
621 log.debug("Calling authenticate()");
622 }
623
624 if (jaspicProvider != null) {
625 jaspicState = getJaspicState(jaspicProvider, request, response, hasAuthConstraint);
626 if (jaspicState == null) {
627 return;
628 }
629 }
630
631 if (jaspicProvider == null && !doAuthenticate(request, response) ||
632 jaspicProvider != null &&
633 !authenticateJaspic(request, response, jaspicState, false)) {
634 if (log.isDebugEnabled()) {
635 log.debug("Failed authenticate() test");
636 }
637 /*
638 * ASSERT: Authenticator already set the appropriate HTTP status
639 * code, so we do not have to do anything special
640 */
641 return;
642 }
643
644 }
645
646 if (constraints != null) {
647 if (log.isDebugEnabled()) {
648 log.debug("Calling accessControl()");
649 }
650 if (!realm.hasResourcePermission(request, response, constraints, this.context)) {
651 if (log.isDebugEnabled()) {
652 log.debug("Failed accessControl() test");
653 }
654 /*
655 * ASSERT: AccessControl method has already set the appropriate
656 * HTTP status code, so we do not have to do anything special
657 */
658 return;
659 }
660 }
661
662 // Any and all specified constraints have been satisfied
663 if (log.isDebugEnabled()) {
664 log.debug("Successfully passed all security constraints");
665 }
666 getNext().invoke(request, response);
667
668 if (jaspicProvider != null) {
669 secureResponseJspic(request, response, jaspicState);
670 }
671 }
672
673
674 protected boolean allowCorsPreflightBypass(Request request) {
675 boolean allowBypass = false;
676
677 if (allowCorsPreflight != AllowCorsPreflight.NEVER) {
678 // First check to see if this is a CORS Preflight request
679 // This is a subset of the tests in CorsFilter.checkRequestType
680 if ("OPTIONS".equals(request.getMethod())) {
681 String originHeader = request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
682 if (originHeader != null &&
683 !originHeader.isEmpty() &&
684 RequestUtil.isValidOrigin(originHeader) &&
685 !RequestUtil.isSameOrigin(request, originHeader)) {
686 String accessControlRequestMethodHeader =
687 request.getHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
688 if (accessControlRequestMethodHeader != null &&
689 !accessControlRequestMethodHeader.isEmpty()) {
690 // This appears to be a CORS Preflight request
691 if (allowCorsPreflight == AllowCorsPreflight.ALWAYS) {
692 allowBypass = true;
693 } else if (allowCorsPreflight == AllowCorsPreflight.FILTER) {
694 if (DispatcherType.REQUEST == request.getDispatcherType()) {
695 // Look at Filter configuration for the Context
696 // Can't cache this unless we add a listener to
697 // the Context to clear the cache on reload
698 for (FilterDef filterDef : request.getContext().findFilterDefs()) {
699 if (CorsFilter.class.getName().equals(filterDef.getFilterClass())) {
700 for (FilterMap filterMap : context.findFilterMaps()) {
701 if (filterMap.getFilterName().equals(filterDef.getFilterName())) {
702 if ((filterMap.getDispatcherMapping() & FilterMap.REQUEST) > 0) {
703 for (String urlPattern : filterMap.getURLPatterns()) {
704 if ("/*".equals(urlPattern)) {
705 allowBypass = true;
706 // No need to check other patterns
707 break;
708 }
709 }
710 }
711 // Found mappings for CORS filter.
712 // No need to look further
713 break;
714 }
715 }
716 // Found the CORS filter. No need to look further.
717 break;
718 }
719 }
720 }
721 } else {
722 // Unexpected enum type
723 }
724 }
725 }
726 }
727 }
728 return allowBypass;
729 }
730
731
732 @Override
733 public boolean authenticate(Request request, HttpServletResponse httpResponse)
734 throws IOException {
735
736 AuthConfigProvider jaspicProvider = getJaspicProvider();
737
738 if (jaspicProvider == null) {
739 return doAuthenticate(request, httpResponse);
740 } else {
741 Response response = request.getResponse();
742 JaspicState jaspicState = getJaspicState(jaspicProvider, request, response, true);
743 if (jaspicState == null) {
744 return false;
745 }
746
747 boolean result = authenticateJaspic(request, response, jaspicState, true);
748
749 secureResponseJspic(request, response, jaspicState);
750
751 return result;
752 }
753 }
754
755
756 private void secureResponseJspic(Request request, Response response, JaspicState state) {
757 try {
758 state.serverAuthContext.secureResponse(state.messageInfo, null);
759 request.setRequest((HttpServletRequest) state.messageInfo.getRequestMessage());
760 response.setResponse((HttpServletResponse) state.messageInfo.getResponseMessage());
761 } catch (AuthException e) {
762 log.warn(sm.getString("authenticator.jaspicSecureResponseFail"), e);
763 }
764 }
765
766
767 private JaspicState getJaspicState(AuthConfigProvider jaspicProvider, Request request,
768 Response response, boolean authMandatory) throws IOException {
769 JaspicState jaspicState = new JaspicState();
770
771 jaspicState.messageInfo =
772 new MessageInfoImpl(request.getRequest(), response.getResponse(), authMandatory);
773
774 try {
775 CallbackHandler callbackHandler = createCallbackHandler();
776 ServerAuthConfig serverAuthConfig = jaspicProvider.getServerAuthConfig(
777 "HttpServlet", jaspicAppContextID, callbackHandler);
778 String authContextID = serverAuthConfig.getAuthContextID(jaspicState.messageInfo);
779 jaspicState.serverAuthContext = serverAuthConfig.getAuthContext(authContextID, null, null);
780 } catch (AuthException e) {
781 log.warn(sm.getString("authenticator.jaspicServerAuthContextFail"), e);
782 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
783 return null;
784 }
785
786 return jaspicState;
787 }
788
789 private CallbackHandler createCallbackHandler() {
790 CallbackHandler callbackHandler = null;
791 if (jaspicCallbackHandlerClass == null) {
792 callbackHandler = CallbackHandlerImpl.getInstance();
793 } else {
794 Class<?> clazz = null;
795 try {
796 clazz = Class.forName(jaspicCallbackHandlerClass, true,
797 Thread.currentThread().getContextClassLoader());
798 } catch (ClassNotFoundException e) {
799 // Proceed with the retry below
800 }
801
802 try {
803 if (clazz == null) {
804 clazz = Class.forName(jaspicCallbackHandlerClass);
805 }
806 callbackHandler = (CallbackHandler)clazz.getConstructor().newInstance();
807 } catch (ReflectiveOperationException e) {
808 throw new SecurityException(e);
809 }
810 }
811
812 return callbackHandler;
813 }
814
815
816 // ------------------------------------------------------ Protected Methods
817
818 /**
819 * Provided for sub-classes to implement their specific authentication
820 * mechanism.
821 *
822 * @param request The request that triggered the authentication
823 * @param response The response associated with the request
824 *
825 * @return {@code true} if the the user was authenticated, otherwise {@code
826 * false}, in which case an authentication challenge will have been
827 * written to the response
828 *
829 * @throws IOException If an I/O problem occurred during the authentication
830 * process
831 */
832 protected abstract boolean doAuthenticate(Request request, HttpServletResponse response)
833 throws IOException;
834
835
836 /**
837 * Does this authenticator require that {@link #authenticate(Request,
838 * HttpServletResponse)} is called to continue an authentication process
839 * that started in a previous request?
840 *
841 * @param request The request currently being processed
842 *
843 * @return {@code true} if authenticate() must be called, otherwise
844 * {@code false}
845 */
846 protected boolean isContinuationRequired(Request request) {
847 return false;
848 }
849
850
851 /**
852 * Look for the X509 certificate chain in the Request under the key
853 * <code>javax.servlet.request.X509Certificate</code>. If not found, trigger
854 * extracting the certificate chain from the Coyote request.
855 *
856 * @param request
857 * Request to be processed
858 *
859 * @return The X509 certificate chain if found, <code>null</code> otherwise.
860 */
861 protected X509Certificate[] getRequestCertificates(final Request request)
862 throws IllegalStateException {
863
864 X509Certificate certs[] =
865 (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR);
866
867 if ((certs == null) || (certs.length < 1)) {
868 try {
869 request.getCoyoteRequest().action(ActionCode.REQ_SSL_CERTIFICATE, null);
870 certs = (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR);
871 } catch (IllegalStateException ise) {
872 // Request body was too large for save buffer
873 // Return null which will trigger an auth failure
874 }
875 }
876
877 return certs;
878 }
879
880 /**
881 * Associate the specified single sign on identifier with the specified
882 * Session.
883 *
884 * @param ssoId
885 * Single sign on identifier
886 * @param session
887 * Session to be associated
888 */
889 protected void associate(String ssoId, Session session) {
890
891 if (sso == null) {
892 return;
893 }
894 sso.associate(ssoId, session);
895
896 }
897
898
899 private boolean authenticateJaspic(Request request, Response response, JaspicState state,
900 boolean requirePrincipal) {
901
902 boolean cachedAuth = checkForCachedAuthentication(request, response, false);
903 Subject client = new Subject();
904 AuthStatus authStatus;
905 try {
906 authStatus = state.serverAuthContext.validateRequest(state.messageInfo, client, null);
907 } catch (AuthException e) {
908 log.debug(sm.getString("authenticator.loginFail"), e);
909 return false;
910 }
911
912 request.setRequest((HttpServletRequest) state.messageInfo.getRequestMessage());
913 response.setResponse((HttpServletResponse) state.messageInfo.getResponseMessage());
914
915 if (authStatus == AuthStatus.SUCCESS) {
916 GenericPrincipal principal = getPrincipal(client);
917 if (log.isDebugEnabled()) {
918 log.debug("Authenticated user: " + principal);
919 }
920 if (principal == null) {
921 request.setUserPrincipal(null);
922 request.setAuthType(null);
923 if (requirePrincipal) {
924 return false;
925 }
926 } else if (cachedAuth == false ||
927 !principal.getUserPrincipal().equals(request.getUserPrincipal())) {
928 // Skip registration if authentication credentials were
929 // cached and the Principal did not change.
930 @SuppressWarnings("rawtypes")// JASPIC API uses raw types
931 Map map = state.messageInfo.getMap();
932 if (map != null && map.containsKey("javax.servlet.http.registerSession")) {
933 register(request, response, principal, "JASPIC", null, null, true, true);
934 } else {
935 register(request, response, principal, "JASPIC", null, null);
936 }
937 }
938 request.setNote(Constants.REQ_JASPIC_SUBJECT_NOTE, client);
939 return true;
940 }
941 return false;
942 }
943
944
945 private GenericPrincipal getPrincipal(Subject subject) {
946 if (subject == null) {
947 return null;
948 }
949
950 Set<GenericPrincipal> principals = subject.getPrivateCredentials(GenericPrincipal.class);
951 if (principals.isEmpty()) {
952 return null;
953 }
954
955 return principals.iterator().next();
956 }
957
958
959 /**
960 * Check to see if the user has already been authenticated earlier in the
961 * processing chain or if there is enough information available to
962 * authenticate the user without requiring further user interaction.
963 *
964 * @param request
965 * The current request
966 * @param response
967 * The current response
968 * @param useSSO
969 * Should information available from SSO be used to attempt to
970 * authenticate the current user?
971 *
972 * @return <code>true</code> if the user was authenticated via the cache,
973 * otherwise <code>false</code>
974 */
975 protected boolean checkForCachedAuthentication(Request request, HttpServletResponse response, boolean useSSO) {
976
977 // Has the user already been authenticated?
978 Principal principal = request.getUserPrincipal();
979 String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
980 if (principal != null) {
981 if (log.isDebugEnabled()) {
982 log.debug(sm.getString("authenticator.check.found", principal.getName()));
983 }
984 // Associate the session with any existing SSO session. Even if
985 // useSSO is false, this will ensure coordinated session
986 // invalidation at log out.
987 if (ssoId != null) {
988 associate(ssoId, request.getSessionInternal(true));
989 }
990 return true;
991 }
992
993 // Is there an SSO session against which we can try to reauthenticate?
994 if (useSSO && ssoId != null) {
995 if (log.isDebugEnabled()) {
996 log.debug(sm.getString("authenticator.check.sso", ssoId));
997 }
998 /*
999 * Try to reauthenticate using data cached by SSO. If this fails,
1000 * either the original SSO logon was of DIGEST or SSL (which we
1001 * can't reauthenticate ourselves because there is no cached
1002 * username and password), or the realm denied the user's
1003 * reauthentication for some reason. In either case we have to
1004 * prompt the user for a logon
1005 */
1006 if (reauthenticateFromSSO(ssoId, request)) {
1007 return true;
1008 }
1009 }
1010
1011 // Has the Connector provided a pre-authenticated Principal that now
1012 // needs to be authorized?
1013 if (request.getCoyoteRequest().getRemoteUserNeedsAuthorization()) {
1014 String username = request.getCoyoteRequest().getRemoteUser().toString();
1015 if (username != null) {
1016 if (log.isDebugEnabled()) {
1017 log.debug(sm.getString("authenticator.check.authorize", username));
1018 }
1019 Principal authorized = context.getRealm().authenticate(username);
1020 if (authorized == null) {
1021 // Realm doesn't recognise user. Create a user with no roles
1022 // from the authenticated user name
1023 if (log.isDebugEnabled()) {
1024 log.debug(sm.getString("authenticator.check.authorizeFail", username));
1025 }
1026 authorized = new GenericPrincipal(username, null, null);
1027 }
1028 String authType = request.getAuthType();
1029 if (authType == null || authType.length() == 0) {
1030 authType = getAuthMethod();
1031 }
1032 register(request, response, authorized, authType, username, null);
1033 return true;
1034 }
1035 }
1036 return false;
1037 }
1038
1039 /**
1040 * Attempts reauthentication to the <code>Realm</code> using the credentials
1041 * included in argument <code>entry</code>.
1042 *
1043 * @param ssoId
1044 * identifier of SingleSignOn session with which the caller is
1045 * associated
1046 * @param request
1047 * the request that needs to be authenticated
1048 * @return <code>true</code> if the reauthentication from SSL occurred
1049 */
1050 protected boolean reauthenticateFromSSO(String ssoId, Request request) {
1051
1052 if (sso == null || ssoId == null) {
1053 return false;
1054 }
1055
1056 boolean reauthenticated = false;
1057
1058 Container parent = getContainer();
1059 if (parent != null) {
1060 Realm realm = parent.getRealm();
1061 if (realm != null) {
1062 reauthenticated = sso.reauthenticate(ssoId, realm, request);
1063 }
1064 }
1065
1066 if (reauthenticated) {
1067 associate(ssoId, request.getSessionInternal(true));
1068
1069 if (log.isDebugEnabled()) {
1070 log.debug("Reauthenticated cached principal '" +
1071 request.getUserPrincipal().getName() +
1072 "' with auth type '" + request.getAuthType() + "'");
1073 }
1074 }
1075
1076 return reauthenticated;
1077 }
1078
1079 /**
1080 * Register an authenticated Principal and authentication type in our
1081 * request, in the current session (if there is one), and with our
1082 * SingleSignOn valve, if there is one. Set the appropriate cookie to be
1083 * returned.
1084 *
1085 * @param request
1086 * The servlet request we are processing
1087 * @param response
1088 * The servlet response we are generating
1089 * @param principal
1090 * The authenticated Principal to be registered
1091 * @param authType
1092 * The authentication type to be registered
1093 * @param username
1094 * Username used to authenticate (if any)
1095 * @param password
1096 * Password used to authenticate (if any)
1097 */
1098 public void register(Request request, HttpServletResponse response, Principal principal,
1099 String authType, String username, String password) {
1100 register(request, response, principal, authType, username, password, alwaysUseSession, cache);
1101 }
1102
1103
1104 /**
1105 * Register an authenticated Principal and authentication type in our
1106 * request, in the current session (if there is one), and with our
1107 * SingleSignOn valve, if there is one. Set the appropriate cookie to be
1108 * returned.
1109 *
1110 * @param request
1111 * The servlet request we are processing
1112 * @param response
1113 * The servlet response we are generating
1114 * @param principal
1115 * The authenticated Principal to be registered
1116 * @param authType
1117 * The authentication type to be registered
1118 * @param username
1119 * Username used to authenticate (if any)
1120 * @param password
1121 * Password used to authenticate (if any)
1122 * @param alwaysUseSession
1123 * Should a session always be used once a user is authenticated?
1124 * @param cache
1125 * Should we cache authenticated Principals if the request is part of an
1126 * HTTP session?
1127 */
1128 protected void register(Request request, HttpServletResponse response, Principal principal,
1129 String authType, String username, String password, boolean alwaysUseSession,
1130 boolean cache) {
1131
1132 if (log.isDebugEnabled()) {
1133 String name = (principal == null) ? "none" : principal.getName();
1134 log.debug("Authenticated '" + name + "' with type '" + authType + "'");
1135 }
1136
1137 // Cache the authentication information in our request
1138 request.setAuthType(authType);
1139 request.setUserPrincipal(principal);
1140
1141 if (sendAuthInfoResponseHeaders
1142 && Boolean.TRUE.equals(request.getAttribute(Globals.REQUEST_FORWARDED_ATTRIBUTE))) {
1143 response.setHeader("remote-user", request.getRemoteUser());
1144 response.setHeader("auth-type", request.getAuthType());
1145 }
1146
1147 Session session = request.getSessionInternal(false);
1148
1149 if (session != null) {
1150 // If the principal is null then this is a logout. No need to change
1151 // the session ID. See BZ 59043.
1152 if (getChangeSessionIdOnAuthentication() && principal != null) {
1153 String newSessionId = changeSessionID(request, session);
1154 // If the current session ID is being tracked, update it.
1155 if (session.getNote(Constants.SESSION_ID_NOTE) != null) {
1156 session.setNote(Constants.SESSION_ID_NOTE, newSessionId);
1157 }
1158 }
1159 } else if (alwaysUseSession) {
1160 session = request.getSessionInternal(true);
1161 }
1162
1163 // Cache the authentication information in our session, if any
1164 if (session != null && cache) {
1165 session.setAuthType(authType);
1166 session.setPrincipal(principal);
1167 }
1168
1169 // Construct a cookie to be returned to the client
1170 if (sso == null) {
1171 return;
1172 }
1173
1174 // Only create a new SSO entry if the SSO did not already set a note
1175 // for an existing entry (as it would do with subsequent requests
1176 // for DIGEST and SSL authenticated contexts)
1177 String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
1178 if (ssoId == null) {
1179 // Construct a cookie to be returned to the client
1180 ssoId = sessionIdGenerator.generateSessionId();
1181 Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, ssoId);
1182 cookie.setMaxAge(-1);
1183 cookie.setPath("/");
1184
1185 // Bugzilla 41217
1186 cookie.setSecure(request.isSecure());
1187
1188 // Bugzilla 34724
1189 String ssoDomain = sso.getCookieDomain();
1190 if (ssoDomain != null) {
1191 cookie.setDomain(ssoDomain);
1192 }
1193
1194 // Configure httpOnly on SSO cookie using same rules as session
1195 // cookies
1196 if (request.getServletContext().getSessionCookieConfig().isHttpOnly() ||
1197 request.getContext().getUseHttpOnly()) {
1198 cookie.setHttpOnly(true);
1199 }
1200
1201 response.addCookie(cookie);
1202
1203 // Register this principal with our SSO valve
1204 sso.register(ssoId, principal, authType, username, password);
1205 request.setNote(Constants.REQ_SSOID_NOTE, ssoId);
1206
1207 } else {
1208 if (principal == null) {
1209 // Registering a programmatic logout
1210 sso.deregister(ssoId);
1211 request.removeNote(Constants.REQ_SSOID_NOTE);
1212 return;
1213 } else {
1214 // Update the SSO session with the latest authentication data
1215 sso.update(ssoId, principal, authType, username, password);
1216 }
1217 }
1218
1219 // Fix for Bug 10040
1220 // Always associate a session with a new SSO reqistration.
1221 // SSO entries are only removed from the SSO registry map when
1222 // associated sessions are destroyed; if a new SSO entry is created
1223 // above for this request and the user never revisits the context, the
1224 // SSO entry will never be cleared if we don't associate the session
1225 if (session == null) {
1226 session = request.getSessionInternal(true);
1227 }
1228 sso.associate(ssoId, session);
1229
1230 }
1231
1232
1233 protected String changeSessionID(Request request, Session session) {
1234 String oldId = null;
1235 if (log.isDebugEnabled()) {
1236 oldId = session.getId();
1237 }
1238 String newId = request.changeSessionId();
1239 if (log.isDebugEnabled()) {
1240 log.debug(sm.getString("authenticator.changeSessionId", oldId, newId));
1241 }
1242 return newId;
1243 }
1244
1245
1246 @Override
1247 public void login(String username, String password, Request request) throws ServletException {
1248 Principal principal = doLogin(request, username, password);
1249 register(request, request.getResponse(), principal, getAuthMethod(), username, password);
1250 }
1251
1252 protected abstract String getAuthMethod();
1253
1254 /**
1255 * Process the login request.
1256 *
1257 * @param request
1258 * Associated request
1259 * @param username
1260 * The user
1261 * @param password
1262 * The password
1263 * @return The authenticated Principal
1264 * @throws ServletException
1265 * No principal was authenticated with the specified credentials
1266 */
1267 protected Principal doLogin(Request request, String username, String password)
1268 throws ServletException {
1269 Principal p = context.getRealm().authenticate(username, password);
1270 if (p == null) {
1271 throw new ServletException(sm.getString("authenticator.loginFail"));
1272 }
1273 return p;
1274 }
1275
1276 @Override
1277 public void logout(Request request) {
1278 AuthConfigProvider provider = getJaspicProvider();
1279 if (provider != null) {
1280 MessageInfo messageInfo = new MessageInfoImpl(request, request.getResponse(), true);
1281 Subject client = (Subject) request.getNote(Constants.REQ_JASPIC_SUBJECT_NOTE);
1282 if (client != null) {
1283 ServerAuthContext serverAuthContext;
1284 try {
1285 ServerAuthConfig serverAuthConfig = provider.getServerAuthConfig("HttpServlet",
1286 jaspicAppContextID, CallbackHandlerImpl.getInstance());
1287 String authContextID = serverAuthConfig.getAuthContextID(messageInfo);
1288 serverAuthContext = serverAuthConfig.getAuthContext(authContextID, null, null);
1289 serverAuthContext.cleanSubject(messageInfo, client);
1290 } catch (AuthException e) {
1291 log.debug(sm.getString("authenticator.jaspicCleanSubjectFail"), e);
1292 }
1293 }
1294 }
1295
1296 Principal p = request.getPrincipal();
1297 if (p instanceof TomcatPrincipal) {
1298 try {
1299 ((TomcatPrincipal) p).logout();
1300 } catch (Throwable t) {
1301 ExceptionUtils.handleThrowable(t);
1302 log.debug(sm.getString("authenticator.tomcatPrincipalLogoutFail"), t);
1303 }
1304 }
1305
1306 register(request, request.getResponse(), null, null, null, null);
1307 }
1308
1309
1310 /**
1311 * Start this component and implement the requirements of
1312 * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
1313 *
1314 * @exception LifecycleException
1315 * if this component detects a fatal error that prevents this
1316 * component from being used
1317 */
1318 @Override
1319 protected synchronized void startInternal() throws LifecycleException {
1320 ServletContext servletContext = context.getServletContext();
1321 jaspicAppContextID = servletContext.getVirtualServerName() + " " +
1322 servletContext.getContextPath();
1323
1324 // Look up the SingleSignOn implementation in our request processing
1325 // path, if there is one
1326 Container parent = context.getParent();
1327 while ((sso == null) && (parent != null)) {
1328 Valve valves[] = parent.getPipeline().getValves();
1329 for (int i = 0; i < valves.length; i++) {
1330 if (valves[i] instanceof SingleSignOn) {
1331 sso = (SingleSignOn) valves[i];
1332 break;
1333 }
1334 }
1335 if (sso == null) {
1336 parent = parent.getParent();
1337 }
1338 }
1339 if (log.isDebugEnabled()) {
1340 if (sso != null) {
1341 log.debug("Found SingleSignOn Valve at " + sso);
1342 } else {
1343 log.debug("No SingleSignOn Valve is present");
1344 }
1345 }
1346
1347 sessionIdGenerator = new StandardSessionIdGenerator();
1348 sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
1349 sessionIdGenerator.setSecureRandomClass(getSecureRandomClass());
1350 sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider());
1351
1352 super.startInternal();
1353 }
1354
1355 /**
1356 * Stop this component and implement the requirements of
1357 * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
1358 *
1359 * @exception LifecycleException
1360 * if this component detects a fatal error that prevents this
1361 * component from being used
1362 */
1363 @Override
1364 protected synchronized void stopInternal() throws LifecycleException {
1365
1366 super.stopInternal();
1367
1368 sso = null;
1369 }
1370
1371
1372 private AuthConfigProvider getJaspicProvider() {
1373 Optional<AuthConfigProvider> provider = jaspicProvider;
1374 if (provider == null) {
1375 provider = findJaspicProvider();
1376 }
1377 return provider.orElse(null);
1378 }
1379
1380
1381 private Optional<AuthConfigProvider> findJaspicProvider() {
1382 AuthConfigFactory factory = AuthConfigFactory.getFactory();
1383 Optional<AuthConfigProvider> provider;
1384 if (factory == null) {
1385 provider = Optional.empty();
1386 } else {
1387 provider = Optional.ofNullable(
1388 factory.getConfigProvider("HttpServlet", jaspicAppContextID, this));
1389 }
1390 jaspicProvider = provider;
1391 return provider;
1392 }
1393
1394
1395 @Override
1396 public void notify(String layer, String appContext) {
1397 findJaspicProvider();
1398 }
1399
1400
1401 private static class JaspicState {
1402 public MessageInfo messageInfo = null;
1403 public ServerAuthContext serverAuthContext = null;
1404 }
1405
1406
1407 protected enum AllowCorsPreflight {
1408 NEVER,
1409 FILTER,
1410 ALWAYS
1411 }
1412 }
1413