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
9
* http://www.apache.org/licenses/LICENSE-2.0
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.
19
package org.apache.catalina.realm;
22
import java.beans.PropertyChangeListener;
23
import java.beans.PropertyChangeSupport;
24
import java.io.IOException;
25
import java.io.UnsupportedEncodingException;
26
import java.nio.charset.Charset;
27
import java.security.MessageDigest;
28
import java.security.NoSuchAlgorithmException;
29
import java.security.Principal;
30
import java.security.cert.X509Certificate;
31
import java.util.ArrayList;
32
import java.util.Locale;
34
import javax.servlet.http.HttpServletResponse;
36
import org.apache.catalina.Container;
37
import org.apache.catalina.Context;
38
import org.apache.catalina.Engine;
39
import org.apache.catalina.Host;
40
import org.apache.catalina.LifecycleException;
41
import org.apache.catalina.LifecycleState;
42
import org.apache.catalina.Realm;
43
import org.apache.catalina.Server;
44
import org.apache.catalina.Service;
45
import org.apache.catalina.Wrapper;
46
import org.apache.catalina.connector.Request;
47
import org.apache.catalina.connector.Response;
48
import org.apache.catalina.deploy.LoginConfig;
49
import org.apache.catalina.deploy.SecurityCollection;
50
import org.apache.catalina.deploy.SecurityConstraint;
51
import org.apache.catalina.mbeans.MBeanUtils;
52
import org.apache.catalina.util.LifecycleMBeanBase;
53
import org.apache.catalina.util.MD5Encoder;
54
import org.apache.catalina.util.SessionConfig;
55
import org.apache.juli.logging.Log;
56
import org.apache.juli.logging.LogFactory;
57
import org.apache.tomcat.util.buf.B2CConverter;
58
import org.apache.tomcat.util.buf.HexUtils;
59
import org.apache.tomcat.util.res.StringManager;
60
import org.ietf.jgss.GSSContext;
61
import org.ietf.jgss.GSSCredential;
62
import org.ietf.jgss.GSSException;
63
import org.ietf.jgss.GSSName;
66
* Simple implementation of <b>Realm</b> that reads an XML file to configure
67
* the valid users, passwords, and roles. The file format (and default file
68
* location) are identical to those currently supported by Tomcat 3.X.
70
* @author Craig R. McClanahan
71
* @version $Id: RealmBase.java 1303339 2012-03-21 10:03:18Z markt $
74
public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
76
private static final Log log = LogFactory.getLog(RealmBase.class);
78
// ----------------------------------------------------- Instance Variables
82
* The Container with which this Realm is associated.
84
protected Container container = null;
90
protected Log containerLog = null;
94
* Digest algorithm used in storing passwords in a non-plaintext format.
95
* Valid values are those accepted for the algorithm name by the
96
* MessageDigest class, or <code>null</code> if no digesting should
99
protected String digest = null;
102
* The encoding charset for the digest.
104
protected String digestEncoding = null;
108
* Descriptive information about this Realm implementation.
110
protected static final String info =
111
"org.apache.catalina.realm.RealmBase/1.0";
115
* The MessageDigest object for digesting user credentials (passwords).
117
protected volatile MessageDigest md = null;
121
* The MD5 helper object for this class.
123
protected static final MD5Encoder md5Encoder = new MD5Encoder();
127
* MD5 message digest provider.
129
protected static volatile MessageDigest md5Helper;
133
* The string manager for this package.
135
protected static final StringManager sm =
136
StringManager.getManager(Constants.Package);
140
* The property change support for this component.
142
protected PropertyChangeSupport support = new PropertyChangeSupport(this);
146
* Should we validate client certificate chains when they are presented?
148
protected boolean validate = true;
151
* The name of the class to use for retrieving user names from X509
154
protected String x509UsernameRetrieverClassName;
157
* The object that will extract user names from X509 client certificates.
159
protected X509UsernameRetriever x509UsernameRetriever;
164
protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
168
* When processing users authenticated via the GSS-API, should any
169
* "@..." be stripped from the end of the user name?
171
protected boolean stripRealmForGss = true;
174
// ------------------------------------------------------------- Properties
178
* Return the Container with which this Realm has been associated.
181
public Container getContainer() {
189
* Set the Container with which this Realm has been associated.
191
* @param container The associated Container
194
public void setContainer(Container container) {
196
Container oldContainer = this.container;
197
this.container = container;
198
support.firePropertyChange("container", oldContainer, this.container);
203
* Return the all roles mode.
205
public String getAllRolesMode() {
207
return allRolesMode.toString();
213
* Set the all roles mode.
215
public void setAllRolesMode(String allRolesMode) {
217
this.allRolesMode = AllRolesMode.toMode(allRolesMode);
222
* Return the digest algorithm used for storing credentials.
224
public String getDigest() {
232
* Set the digest algorithm used for storing credentials.
234
* @param digest The new digest algorithm
236
public void setDigest(String digest) {
238
this.digest = digest;
243
* Returns the digest encoding charset.
245
* @return The charset (may be null) for platform default
247
public String getDigestEncoding() {
248
return digestEncoding;
252
* Sets the digest encoding charset.
254
* @param charset The charset (null for platform default)
256
public void setDigestEncoding(String charset) {
257
digestEncoding = charset;
260
protected Charset getDigestCharset() throws UnsupportedEncodingException {
261
if (digestEncoding == null) {
262
return Charset.defaultCharset();
264
return B2CConverter.getCharset(getDigestEncoding());
269
* Return descriptive information about this Realm implementation and
270
* the corresponding version number, in the format
271
* <code><description>/<version></code>.
274
public String getInfo() {
282
* Return the "validate certificate chains" flag.
284
public boolean getValidate() {
286
return (this.validate);
292
* Set the "validate certificate chains" flag.
294
* @param validate The new validate certificate chains flag
296
public void setValidate(boolean validate) {
298
this.validate = validate;
303
* Gets the name of the class that will be used to extract user names
304
* from X509 client certificates.
305
* @return The name of the class that will be used to extract user names
306
* from X509 client certificates.
308
public String getX509UsernameRetrieverClassName() {
309
return x509UsernameRetrieverClassName;
313
* Sets the name of the class that will be used to extract user names
314
* from X509 client certificates. The class must implement
315
* (@link X509UsernameRetriever}.
317
* @param className The name of the class that will be used to extract user names
318
* from X509 client certificates.
320
public void setX509UsernameRetrieverClassName(String className) {
321
this.x509UsernameRetrieverClassName = className;
324
public boolean isStripRealmForGss() {
325
return stripRealmForGss;
329
public void setStripRealmForGss(boolean stripRealmForGss) {
330
this.stripRealmForGss = stripRealmForGss;
334
// --------------------------------------------------------- Public Methods
338
* Add a property change listener to this component.
340
* @param listener The listener to add
343
public void addPropertyChangeListener(PropertyChangeListener listener) {
345
support.addPropertyChangeListener(listener);
351
* Return the Principal associated with the specified username and
352
* credentials, if there is one; otherwise return <code>null</code>.
354
* @param username Username of the Principal to look up
355
* @param credentials Password or other credentials to use in
356
* authenticating this username
359
public Principal authenticate(String username, String credentials) {
361
String serverCredentials = getPassword(username);
364
if ( serverCredentials == null ) {
366
} else if(hasMessageDigest()) {
367
validated = serverCredentials.equalsIgnoreCase(digest(credentials));
369
validated = serverCredentials.equals(credentials);
372
if (containerLog.isTraceEnabled()) {
373
containerLog.trace(sm.getString("realmBase.authenticateFailure",
378
if (containerLog.isTraceEnabled()) {
379
containerLog.trace(sm.getString("realmBase.authenticateSuccess",
383
return getPrincipal(username);
388
* Return the Principal associated with the specified username, which
389
* matches the digest calculated using the given parameters using the
390
* method described in RFC 2069; otherwise return <code>null</code>.
392
* @param username Username of the Principal to look up
393
* @param clientDigest Digest which has been submitted by the client
394
* @param nonce Unique (or supposedly unique) token which has been used
396
* @param realm Realm name
397
* @param md5a2 Second MD5 digest used to calculate the digest :
398
* MD5(Method + ":" + uri)
401
public Principal authenticate(String username, String clientDigest,
402
String nonce, String nc, String cnonce,
403
String qop, String realm,
406
// In digest auth, digests are always lower case
407
String md5a1 = getDigest(username, realm).toLowerCase(Locale.ENGLISH);
410
String serverDigestValue;
412
serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2;
414
serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" +
415
cnonce + ":" + qop + ":" + md5a2;
418
byte[] valueBytes = null;
420
valueBytes = serverDigestValue.getBytes(getDigestCharset());
421
} catch (UnsupportedEncodingException uee) {
422
log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
423
throw new IllegalArgumentException(uee.getMessage());
426
String serverDigest = null;
428
synchronized(md5Helper) {
429
serverDigest = md5Encoder.encode(md5Helper.digest(valueBytes));
432
if (log.isDebugEnabled()) {
433
log.debug("Digest : " + clientDigest + " Username:" + username
434
+ " ClientSigest:" + clientDigest + " nonce:" + nonce
435
+ " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop
436
+ " realm:" + realm + "md5a2:" + md5a2
437
+ " Server digest:" + serverDigest);
440
if (serverDigest.equals(clientDigest)) {
441
return getPrincipal(username);
449
* Return the Principal associated with the specified chain of X509
450
* client certificates. If there is none, return <code>null</code>.
452
* @param certs Array of client certificates, with the first one in
453
* the array being the certificate of the client itself.
456
public Principal authenticate(X509Certificate certs[]) {
458
if ((certs == null) || (certs.length < 1))
461
// Check the validity of each certificate in the chain
462
if (log.isDebugEnabled())
463
log.debug("Authenticating client certificate chain");
465
for (int i = 0; i < certs.length; i++) {
466
if (log.isDebugEnabled())
467
log.debug(" Checking validity for '" +
468
certs[i].getSubjectDN().getName() + "'");
470
certs[i].checkValidity();
471
} catch (Exception e) {
472
if (log.isDebugEnabled())
473
log.debug(" Validity exception", e);
479
// Check the existence of the client Principal in our database
480
return (getPrincipal(certs[0]));
489
public Principal authenticate(GSSContext gssContext, boolean storeCred) {
490
if (gssContext.isEstablished()) {
491
GSSName gssName = null;
493
gssName = gssContext.getSrcName();
494
} catch (GSSException e) {
495
log.warn(sm.getString("realmBase.gssNameFail"), e);
498
if (gssName!= null) {
499
String name = gssName.toString();
501
if (isStripRealmForGss()) {
502
int i = name.indexOf('@');
504
// Zero so we don;t leave a zero length name
505
name = name.substring(0, i);
508
GSSCredential gssCredential = null;
509
if (storeCred && gssContext.getCredDelegState()) {
511
gssCredential = gssContext.getDelegCred();
512
} catch (GSSException e) {
513
if (log.isDebugEnabled()) {
514
log.debug(sm.getString(
515
"realmBase.delegatedCredentialFail", name),
520
return getPrincipal(name, gssCredential);
524
// Fail in all other cases
530
* Execute a periodic task, such as reloading, etc. This method will be
531
* invoked inside the classloading context of this container. Unexpected
532
* throwables will be caught and logged.
535
public void backgroundProcess() {
536
// NOOP in base class
541
* Return the SecurityConstraints configured to guard the request URI for
542
* this request, or <code>null</code> if there is no such constraint.
544
* @param request Request we are processing
545
* @param context Context the Request is mapped to
548
public SecurityConstraint [] findSecurityConstraints(Request request,
551
ArrayList<SecurityConstraint> results = null;
552
// Are there any defined security constraints?
553
SecurityConstraint constraints[] = context.findConstraints();
554
if ((constraints == null) || (constraints.length == 0)) {
555
if (log.isDebugEnabled())
556
log.debug(" No applicable constraints defined");
560
// Check each defined security constraint
561
String uri = request.getRequestPathMB().toString();
562
// Bug47080 - in rare cases this may be null
563
// Mapper treats as '/' do the same to prevent NPE
568
String method = request.getMethod();
570
boolean found = false;
571
for (i = 0; i < constraints.length; i++) {
572
SecurityCollection [] collection = constraints[i].findCollections();
574
// If collection is null, continue to avoid an NPE
575
// See Bugzilla 30624
576
if ( collection == null) {
580
if (log.isDebugEnabled()) {
581
log.debug(" Checking constraint '" + constraints[i] +
582
"' against " + method + " " + uri + " --> " +
583
constraints[i].included(uri, method));
586
for(int j=0; j < collection.length; j++){
587
String [] patterns = collection[j].findPatterns();
589
// If patterns is null, continue to avoid an NPE
590
// See Bugzilla 30624
591
if ( patterns == null) {
595
for(int k=0; k < patterns.length; k++) {
596
if(uri.equals(patterns[k])) {
598
if(collection[j].findMethod(method)) {
599
if(results == null) {
600
results = new ArrayList<SecurityConstraint>();
602
results.add(constraints[i]);
610
return resultsToArray(results);
615
for (i = 0; i < constraints.length; i++) {
616
SecurityCollection [] collection = constraints[i].findCollections();
618
// If collection is null, continue to avoid an NPE
619
// See Bugzilla 30624
620
if ( collection == null) {
624
if (log.isDebugEnabled()) {
625
log.debug(" Checking constraint '" + constraints[i] +
626
"' against " + method + " " + uri + " --> " +
627
constraints[i].included(uri, method));
630
for(int j=0; j < collection.length; j++){
631
String [] patterns = collection[j].findPatterns();
633
// If patterns is null, continue to avoid an NPE
634
// See Bugzilla 30624
635
if ( patterns == null) {
639
boolean matched = false;
641
for(int k=0; k < patterns.length; k++) {
642
String pattern = patterns[k];
643
if(pattern.startsWith("/") && pattern.endsWith("/*") &&
644
pattern.length() >= longest) {
646
if(pattern.length() == 2) {
648
length = pattern.length();
649
} else if(pattern.regionMatches(0,uri,0,
650
pattern.length()-1) ||
651
(pattern.length()-2 == uri.length() &&
652
pattern.regionMatches(0,uri,0,
653
pattern.length()-2))) {
655
length = pattern.length();
661
if(length > longest) {
662
if(results != null) {
667
if(collection[j].findMethod(method)) {
668
if(results == null) {
669
results = new ArrayList<SecurityConstraint>();
671
results.add(constraints[i]);
678
return resultsToArray(results);
681
for (i = 0; i < constraints.length; i++) {
682
SecurityCollection [] collection = constraints[i].findCollections();
684
// If collection is null, continue to avoid an NPE
685
// See Bugzilla 30624
686
if ( collection == null) {
690
if (log.isDebugEnabled()) {
691
log.debug(" Checking constraint '" + constraints[i] +
692
"' against " + method + " " + uri + " --> " +
693
constraints[i].included(uri, method));
696
boolean matched = false;
698
for(int j=0; j < collection.length; j++){
699
String [] patterns = collection[j].findPatterns();
701
// If patterns is null, continue to avoid an NPE
702
// See Bugzilla 30624
703
if ( patterns == null) {
707
for(int k=0; k < patterns.length && !matched; k++) {
708
String pattern = patterns[k];
709
if(pattern.startsWith("*.")){
710
int slash = uri.lastIndexOf("/");
711
int dot = uri.lastIndexOf(".");
712
if(slash >= 0 && dot > slash &&
713
dot != uri.length()-1 &&
714
uri.length()-dot == pattern.length()-1) {
715
if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
725
if(collection[pos].findMethod(method)) {
726
if(results == null) {
727
results = new ArrayList<SecurityConstraint>();
729
results.add(constraints[i]);
735
return resultsToArray(results);
738
for (i = 0; i < constraints.length; i++) {
739
SecurityCollection [] collection = constraints[i].findCollections();
741
// If collection is null, continue to avoid an NPE
742
// See Bugzilla 30624
743
if ( collection == null) {
747
if (log.isDebugEnabled()) {
748
log.debug(" Checking constraint '" + constraints[i] +
749
"' against " + method + " " + uri + " --> " +
750
constraints[i].included(uri, method));
753
for(int j=0; j < collection.length; j++){
754
String [] patterns = collection[j].findPatterns();
756
// If patterns is null, continue to avoid an NPE
757
// See Bugzilla 30624
758
if ( patterns == null) {
762
boolean matched = false;
763
for(int k=0; k < patterns.length && !matched; k++) {
764
String pattern = patterns[k];
765
if(pattern.equals("/")){
770
if(results == null) {
771
results = new ArrayList<SecurityConstraint>();
773
results.add(constraints[i]);
778
if(results == null) {
779
// No applicable security constraint was found
780
if (log.isDebugEnabled())
781
log.debug(" No applicable constraint located");
783
return resultsToArray(results);
787
* Convert an ArrayList to a SecurityContraint [].
789
private SecurityConstraint [] resultsToArray(
790
ArrayList<SecurityConstraint> results) {
791
if(results == null) {
794
SecurityConstraint [] array = new SecurityConstraint[results.size()];
795
results.toArray(array);
801
* Perform access control based on the specified authorization constraint.
802
* Return <code>true</code> if this constraint is satisfied and processing
803
* should continue, or <code>false</code> otherwise.
805
* @param request Request we are processing
806
* @param response Response we are creating
807
* @param constraints Security constraint we are enforcing
808
* @param context The Context to which client of this class is attached.
810
* @exception IOException if an input/output error occurs
813
public boolean hasResourcePermission(Request request,
815
SecurityConstraint []constraints,
819
if (constraints == null || constraints.length == 0)
822
// Specifically allow access to the form login and form error pages
823
// and the "j_security_check" action
824
LoginConfig config = context.getLoginConfig();
825
if ((config != null) &&
826
(Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
827
String requestURI = request.getRequestPathMB().toString();
828
String loginPage = config.getLoginPage();
829
if (loginPage.equals(requestURI)) {
830
if (log.isDebugEnabled())
831
log.debug(" Allow access to login page " + loginPage);
834
String errorPage = config.getErrorPage();
835
if (errorPage.equals(requestURI)) {
836
if (log.isDebugEnabled())
837
log.debug(" Allow access to error page " + errorPage);
840
if (requestURI.endsWith(Constants.FORM_ACTION)) {
841
if (log.isDebugEnabled())
842
log.debug(" Allow access to username/password submission");
847
// Which user principal have we already authenticated?
848
Principal principal = request.getPrincipal();
849
boolean status = false;
850
boolean denyfromall = false;
851
for(int i=0; i < constraints.length; i++) {
852
SecurityConstraint constraint = constraints[i];
855
if (constraint.getAllRoles()) {
856
// * means all roles defined in web.xml
857
roles = request.getContext().findSecurityRoles();
859
roles = constraint.findAuthRoles();
863
roles = new String[0];
865
if (log.isDebugEnabled())
866
log.debug(" Checking roles " + principal);
868
if (roles.length == 0 && !constraint.getAllRoles()) {
869
if(constraint.getAuthConstraint()) {
870
if( log.isDebugEnabled() )
871
log.debug("No roles");
872
status = false; // No listed roles means no access at all
877
if(log.isDebugEnabled())
878
log.debug("Passing all access");
880
} else if (principal == null) {
881
if (log.isDebugEnabled())
882
log.debug(" No user authenticated, cannot grant access");
884
for (int j = 0; j < roles.length; j++) {
885
if (hasRole(null, principal, roles[j])) {
887
if( log.isDebugEnabled() )
888
log.debug( "Role found: " + roles[j]);
890
else if( log.isDebugEnabled() )
891
log.debug( "No role found: " + roles[j]);
896
if (!denyfromall && allRolesMode != AllRolesMode.STRICT_MODE &&
897
!status && principal != null) {
898
if (log.isDebugEnabled()) {
899
log.debug("Checking for all roles mode: " + allRolesMode);
901
// Check for an all roles(role-name="*")
902
for (int i = 0; i < constraints.length; i++) {
903
SecurityConstraint constraint = constraints[i];
905
// If the all roles mode exists, sets
906
if (constraint.getAllRoles()) {
907
if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
908
if (log.isDebugEnabled()) {
909
log.debug("Granting access for role-name=*, auth-only");
915
// For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
916
roles = request.getContext().findSecurityRoles();
917
if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
918
if (log.isDebugEnabled()) {
919
log.debug("Granting access for role-name=*, strict auth-only");
928
// Return a "Forbidden" message denying access to this resource
931
(HttpServletResponse.SC_FORBIDDEN,
932
sm.getString("realmBase.forbidden"));
940
* Return <code>true</code> if the specified Principal has the specified
941
* security role, within the context of this Realm; otherwise return
942
* <code>false</code>. This method can be overridden by Realm
943
* implementations, but the default is adequate when an instance of
944
* <code>GenericPrincipal</code> is used to represent authenticated
945
* Principals from this Realm.
947
* @param principal Principal for whom the role is to be checked
948
* @param role Security role to be checked
951
public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
952
// Check for a role alias defined in a <security-role-ref> element
953
if (wrapper != null) {
954
String realRole = wrapper.findSecurityReference(role);
955
if (realRole != null)
959
// Should be overridden in JAASRealm - to avoid pretty inefficient conversions
960
if ((principal == null) || (role == null) ||
961
!(principal instanceof GenericPrincipal))
964
GenericPrincipal gp = (GenericPrincipal) principal;
965
boolean result = gp.hasRole(role);
966
if (log.isDebugEnabled()) {
967
String name = principal.getName();
969
log.debug(sm.getString("realmBase.hasRoleSuccess", name, role));
971
log.debug(sm.getString("realmBase.hasRoleFailure", name, role));
979
* Enforce any user data constraint required by the security constraint
980
* guarding this request URI. Return <code>true</code> if this constraint
981
* was not violated and processing should continue, or <code>false</code>
982
* if we have created a response already.
984
* @param request Request we are processing
985
* @param response Response we are creating
986
* @param constraints Security constraint being checked
988
* @exception IOException if an input/output error occurs
991
public boolean hasUserDataPermission(Request request,
993
SecurityConstraint []constraints)
996
// Is there a relevant user data constraint?
997
if (constraints == null || constraints.length == 0) {
998
if (log.isDebugEnabled())
999
log.debug(" No applicable security constraint defined");
1002
for(int i=0; i < constraints.length; i++) {
1003
SecurityConstraint constraint = constraints[i];
1004
String userConstraint = constraint.getUserConstraint();
1005
if (userConstraint == null) {
1006
if (log.isDebugEnabled())
1007
log.debug(" No applicable user data constraint defined");
1010
if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
1011
if (log.isDebugEnabled())
1012
log.debug(" User data constraint has no restrictions");
1017
// Validate the request against the user data constraint
1018
if (request.getRequest().isSecure()) {
1019
if (log.isDebugEnabled())
1020
log.debug(" User data constraint already satisfied");
1023
// Initialize variables we need to determine the appropriate action
1024
int redirectPort = request.getConnector().getRedirectPort();
1026
// Is redirecting disabled?
1027
if (redirectPort <= 0) {
1028
if (log.isDebugEnabled())
1029
log.debug(" SSL redirect is disabled");
1031
(HttpServletResponse.SC_FORBIDDEN,
1032
request.getRequestURI());
1036
// Redirect to the corresponding SSL port
1037
StringBuilder file = new StringBuilder();
1038
String protocol = "https";
1039
String host = request.getServerName();
1041
file.append(protocol).append("://").append(host);
1043
if(redirectPort != 443) {
1044
file.append(":").append(redirectPort);
1047
file.append(request.getRequestURI());
1048
String requestedSessionId = request.getRequestedSessionId();
1049
if ((requestedSessionId != null) &&
1050
request.isRequestedSessionIdFromURL()) {
1052
file.append(SessionConfig.getSessionUriParamName(
1053
request.getContext()));
1055
file.append(requestedSessionId);
1057
String queryString = request.getQueryString();
1058
if (queryString != null) {
1060
file.append(queryString);
1062
if (log.isDebugEnabled())
1063
log.debug(" Redirecting to " + file.toString());
1064
response.sendRedirect(file.toString());
1071
* Remove a property change listener from this component.
1073
* @param listener The listener to remove
1076
public void removePropertyChangeListener(PropertyChangeListener listener) {
1078
support.removePropertyChangeListener(listener);
1084
protected void initInternal() throws LifecycleException {
1086
super.initInternal();
1088
// We want logger as soon as possible
1089
if (container != null) {
1090
this.containerLog = container.getLogger();
1093
x509UsernameRetriever = createUsernameRetriever(x509UsernameRetrieverClassName);
1097
* Prepare for the beginning of active use of the public methods of this
1098
* component and implement the requirements of
1099
* {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
1101
* @exception LifecycleException if this component detects a fatal error
1102
* that prevents this component from being used
1105
protected void startInternal() throws LifecycleException {
1107
// Create a MessageDigest instance for credentials, if desired
1108
if (digest != null) {
1110
md = MessageDigest.getInstance(digest);
1111
} catch (NoSuchAlgorithmException e) {
1112
throw new LifecycleException
1113
(sm.getString("realmBase.algorithm", digest), e);
1117
setState(LifecycleState.STARTING);
1122
* Gracefully terminate the active use of the public methods of this
1123
* component and implement the requirements of
1124
* {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
1126
* @exception LifecycleException if this component detects a fatal error
1127
* that needs to be reported
1130
protected void stopInternal() throws LifecycleException {
1132
setState(LifecycleState.STOPPING);
1134
// Clean up allocated resources
1140
* Return a String representation of this component.
1143
public String toString() {
1144
StringBuilder sb = new StringBuilder("Realm[");
1145
sb.append(getName());
1147
return sb.toString();
1151
// ------------------------------------------------------ Protected Methods
1155
* Digest the password using the specified algorithm and
1156
* convert the result to a corresponding hexadecimal string.
1157
* If exception, the plain credentials string is returned.
1159
* @param credentials Password or other credentials to use in
1160
* authenticating this username
1162
protected String digest(String credentials) {
1164
// If no MessageDigest instance is specified, return unchanged
1165
if (hasMessageDigest() == false)
1166
return (credentials);
1168
// Digest the user credentials and return as hexadecimal
1169
synchronized (this) {
1173
byte[] bytes = null;
1175
bytes = credentials.getBytes(getDigestCharset());
1176
} catch (UnsupportedEncodingException uee) {
1177
log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
1178
throw new IllegalArgumentException(uee.getMessage());
1182
return (HexUtils.toHexString(md.digest()));
1183
} catch (Exception e) {
1184
log.error(sm.getString("realmBase.digest"), e);
1185
return (credentials);
1191
protected boolean hasMessageDigest() {
1192
return !(md == null);
1196
* Return the digest associated with given principal's user name.
1198
protected String getDigest(String username, String realmName) {
1199
if (md5Helper == null) {
1201
md5Helper = MessageDigest.getInstance("MD5");
1202
} catch (NoSuchAlgorithmException e) {
1203
log.error("Couldn't get MD5 digest: ", e);
1204
throw new IllegalStateException(e.getMessage());
1208
if (hasMessageDigest()) {
1209
// Use pre-generated digest
1210
return getPassword(username);
1213
String digestValue = username + ":" + realmName + ":"
1214
+ getPassword(username);
1216
byte[] valueBytes = null;
1218
valueBytes = digestValue.getBytes(getDigestCharset());
1219
} catch (UnsupportedEncodingException uee) {
1220
log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
1221
throw new IllegalArgumentException(uee.getMessage());
1224
byte[] digest = null;
1226
synchronized(md5Helper) {
1227
digest = md5Helper.digest(valueBytes);
1230
return md5Encoder.encode(digest);
1235
* Return a short name for this Realm implementation, for use in
1238
protected abstract String getName();
1242
* Return the password associated with the given principal's user name.
1244
protected abstract String getPassword(String username);
1248
* Return the Principal associated with the given certificate.
1250
protected Principal getPrincipal(X509Certificate usercert) {
1251
String username = x509UsernameRetriever.getUsername(usercert);
1253
if(log.isDebugEnabled())
1254
log.debug(sm.getString("realmBase.gotX509Username", username));
1256
return(getPrincipal(username));
1261
* Return the Principal associated with the given user name.
1263
protected abstract Principal getPrincipal(String username);
1266
protected Principal getPrincipal(String username,
1267
GSSCredential gssCredential) {
1268
Principal p = getPrincipal(username);
1270
if (p instanceof GenericPrincipal) {
1271
((GenericPrincipal) p).setGssCredential(gssCredential);
1278
* Return the Server object that is the ultimate parent for the container
1279
* with which this Realm is associated. If the server cannot be found (eg
1280
* because the container hierarchy is not complete), <code>null</code> is
1283
protected Server getServer() {
1284
Container c = container;
1285
if (c instanceof Context) {
1288
if (c instanceof Host) {
1291
if (c instanceof Engine) {
1292
Service s = ((Engine)c).getService();
1294
return s.getServer();
1301
// --------------------------------------------------------- Static Methods
1305
* Digest password using the algorithm specified and
1306
* convert the result to a corresponding hex string.
1307
* If exception, the plain credentials string is returned
1309
* @param credentials Password or other credentials to use in
1310
* authenticating this username
1311
* @param algorithm Algorithm used to do the digest
1312
* @param encoding Character encoding of the string to digest
1314
public static final String Digest(String credentials, String algorithm,
1318
// Obtain a new message digest with "digest" encryption
1320
(MessageDigest) MessageDigest.getInstance(algorithm).clone();
1322
// encode the credentials
1323
// Should use the digestEncoding, but that's not a static field
1324
if (encoding == null) {
1325
md.update(credentials.getBytes());
1327
md.update(credentials.getBytes(encoding));
1330
// Digest the credentials and return as hexadecimal
1331
return (HexUtils.toHexString(md.digest()));
1332
} catch(Exception ex) {
1341
* Digest password using the algorithm specified and
1342
* convert the result to a corresponding hex string.
1343
* If exception, the plain credentials string is returned
1345
public static void main(String args[]) {
1347
String encoding = null;
1348
int firstCredentialArg = 2;
1350
if (args.length > 4 && args[2].equalsIgnoreCase("-e")) {
1352
firstCredentialArg = 4;
1355
if(args.length > firstCredentialArg && args[0].equalsIgnoreCase("-a")) {
1356
for(int i=firstCredentialArg; i < args.length ; i++){
1357
System.out.print(args[i]+":");
1358
System.out.println(Digest(args[i], args[1], encoding));
1362
("Usage: RealmBase -a <algorithm> [-e <encoding>] <credentials>");
1368
// -------------------- JMX and Registration --------------------
1371
public String getObjectNameKeyProperties() {
1373
StringBuilder keyProperties = new StringBuilder("type=Realm");
1374
keyProperties.append(getRealmSuffix());
1375
keyProperties.append(MBeanUtils.getContainerKeyProperties(container));
1377
return keyProperties.toString();
1381
public String getDomainInternal() {
1382
return MBeanUtils.getDomain(container);
1385
protected String realmPath = "/realm0";
1387
public String getRealmPath() {
1391
public void setRealmPath(String theRealmPath) {
1392
realmPath = theRealmPath;
1395
protected String getRealmSuffix() {
1396
return ",realmPath=" + getRealmPath();
1400
protected static class AllRolesMode {
1402
private String name;
1403
/** Use the strict servlet spec interpretation which requires that the user
1404
* have one of the web-app/security-role/role-name
1406
public static final AllRolesMode STRICT_MODE = new AllRolesMode("strict");
1407
/** Allow any authenticated user
1409
public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode("authOnly");
1410
/** Allow any authenticated user only if there are no web-app/security-roles
1412
public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode("strictAuthOnly");
1414
static AllRolesMode toMode(String name)
1417
if( name.equalsIgnoreCase(STRICT_MODE.name) )
1419
else if( name.equalsIgnoreCase(AUTH_ONLY_MODE.name) )
1420
mode = AUTH_ONLY_MODE;
1421
else if( name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name) )
1422
mode = STRICT_AUTH_ONLY_MODE;
1424
throw new IllegalStateException("Unknown mode, must be one of: strict, authOnly, strictAuthOnly");
1428
private AllRolesMode(String name)
1434
public boolean equals(Object o)
1436
boolean equals = false;
1437
if( o instanceof AllRolesMode )
1439
AllRolesMode mode = (AllRolesMode) o;
1440
equals = name.equals(mode.name);
1445
public int hashCode()
1447
return name.hashCode();
1450
public String toString()
1456
private static X509UsernameRetriever createUsernameRetriever(String className)
1457
throws LifecycleException {
1458
if(null == className || "".equals(className.trim()))
1459
return new X509SubjectDnRetriever();
1462
@SuppressWarnings("unchecked")
1463
Class<? extends X509UsernameRetriever> clazz = (Class<? extends X509UsernameRetriever>)Class.forName(className);
1464
return clazz.newInstance();
1465
} catch (ClassNotFoundException e) {
1466
throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.ClassNotFoundException", className), e);
1467
} catch (InstantiationException e) {
1468
throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.InstantiationException", className), e);
1469
} catch (IllegalAccessException e) {
1470
throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.IllegalAccessException", className), e);
1471
} catch (ClassCastException e) {
1472
throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.ClassCastException", className), e);