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.security.MessageDigest;
27
import java.security.NoSuchAlgorithmException;
28
import java.security.Principal;
29
import java.security.cert.X509Certificate;
30
import java.util.ArrayList;
32
import javax.management.Attribute;
33
import javax.management.MBeanRegistration;
34
import javax.management.MBeanServer;
35
import javax.management.ObjectName;
36
import javax.servlet.http.HttpServletResponse;
38
import org.apache.catalina.Container;
39
import org.apache.catalina.Context;
40
import org.apache.catalina.Globals;
41
import org.apache.catalina.Lifecycle;
42
import org.apache.catalina.LifecycleException;
43
import org.apache.catalina.LifecycleListener;
44
import org.apache.catalina.Realm;
45
import org.apache.catalina.connector.Request;
46
import org.apache.catalina.connector.Response;
47
import org.apache.catalina.core.ContainerBase;
48
import org.apache.catalina.deploy.LoginConfig;
49
import org.apache.catalina.deploy.SecurityConstraint;
50
import org.apache.catalina.deploy.SecurityCollection;
51
import org.apache.catalina.util.HexUtils;
52
import org.apache.catalina.util.LifecycleSupport;
53
import org.apache.catalina.util.MD5Encoder;
54
import org.apache.catalina.util.StringManager;
55
import org.apache.juli.logging.Log;
56
import org.apache.juli.logging.LogFactory;
57
import org.apache.tomcat.util.modeler.Registry;
60
* Simple implementation of <b>Realm</b> that reads an XML file to configure
61
* the valid users, passwords, and roles. The file format (and default file
62
* location) are identical to those currently supported by Tomcat 3.X.
64
* @author Craig R. McClanahan
65
* @version $Id: RealmBase.java 1158180 2011-08-16 10:11:43Z markt $
68
public abstract class RealmBase
69
implements Lifecycle, Realm, MBeanRegistration {
71
private static Log log = LogFactory.getLog(RealmBase.class);
73
// ----------------------------------------------------- Instance Variables
77
* The Container with which this Realm is associated.
79
protected Container container = null;
85
protected Log containerLog = null;
89
* Digest algorithm used in storing passwords in a non-plaintext format.
90
* Valid values are those accepted for the algorithm name by the
91
* MessageDigest class, or <code>null</code> if no digesting should
94
protected String digest = null;
97
* The encoding charset for the digest.
99
protected String digestEncoding = null;
103
* Descriptive information about this Realm implementation.
105
protected static final String info =
106
"org.apache.catalina.realm.RealmBase/1.0";
110
* The lifecycle event support for this component.
112
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
116
* The MessageDigest object for digesting user credentials (passwords).
118
protected MessageDigest md = null;
122
* The MD5 helper object for this class.
124
protected static final MD5Encoder md5Encoder = new MD5Encoder();
128
* MD5 message digest provider.
130
protected static MessageDigest md5Helper;
134
* The string manager for this package.
136
protected static StringManager sm =
137
StringManager.getManager(Constants.Package);
141
* Has this component been started?
143
protected boolean started = false;
147
* The property change support for this component.
149
protected PropertyChangeSupport support = new PropertyChangeSupport(this);
153
* Should we validate client certificate chains when they are presented?
155
protected boolean validate = true;
161
protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
164
// ------------------------------------------------------------- Properties
168
* Return the Container with which this Realm has been associated.
170
public Container getContainer() {
178
* Set the Container with which this Realm has been associated.
180
* @param container The associated Container
182
public void setContainer(Container container) {
184
Container oldContainer = this.container;
185
this.container = container;
186
support.firePropertyChange("container", oldContainer, this.container);
191
* Return the all roles mode.
193
public String getAllRolesMode() {
195
return allRolesMode.toString();
201
* Set the all roles mode.
203
public void setAllRolesMode(String allRolesMode) {
205
this.allRolesMode = AllRolesMode.toMode(allRolesMode);
210
* Return the digest algorithm used for storing credentials.
212
public String getDigest() {
220
* Set the digest algorithm used for storing credentials.
222
* @param digest The new digest algorithm
224
public void setDigest(String digest) {
226
this.digest = digest;
231
* Returns the digest encoding charset.
233
* @return The charset (may be null) for platform default
235
public String getDigestEncoding() {
236
return digestEncoding;
240
* Sets the digest encoding charset.
242
* @param charset The charset (null for platform default)
244
public void setDigestEncoding(String charset) {
245
digestEncoding = charset;
249
* Return descriptive information about this Realm implementation and
250
* the corresponding version number, in the format
251
* <code><description>/<version></code>.
253
public String getInfo() {
261
* Return the "validate certificate chains" flag.
263
public boolean getValidate() {
265
return (this.validate);
271
* Set the "validate certificate chains" flag.
273
* @param validate The new validate certificate chains flag
275
public void setValidate(boolean validate) {
277
this.validate = validate;
282
// --------------------------------------------------------- Public Methods
287
* Add a property change listener to this component.
289
* @param listener The listener to add
291
public void addPropertyChangeListener(PropertyChangeListener listener) {
293
support.addPropertyChangeListener(listener);
299
* Return the Principal associated with the specified username and
300
* credentials, if there is one; otherwise return <code>null</code>.
302
* @param username Username of the Principal to look up
303
* @param credentials Password or other credentials to use in
304
* authenticating this username
306
public Principal authenticate(String username, String credentials) {
308
String serverCredentials = getPassword(username);
311
if ( serverCredentials == null ) {
313
} else if(hasMessageDigest()) {
314
validated = serverCredentials.equalsIgnoreCase(digest(credentials));
316
validated = serverCredentials.equals(credentials);
319
if (containerLog.isTraceEnabled()) {
320
containerLog.trace(sm.getString("realmBase.authenticateFailure",
325
if (containerLog.isTraceEnabled()) {
326
containerLog.trace(sm.getString("realmBase.authenticateSuccess",
330
return getPrincipal(username);
335
* Return the Principal associated with the specified username and
336
* credentials, if there is one; otherwise return <code>null</code>.
338
* @param username Username of the Principal to look up
339
* @param credentials Password or other credentials to use in
340
* authenticating this username
342
public Principal authenticate(String username, byte[] credentials) {
344
return (authenticate(username, credentials.toString()));
350
* Return the Principal associated with the specified username, which
351
* matches the digest calculated using the given parameters using the
352
* method described in RFC 2069; otherwise return <code>null</code>.
354
* @param username Username of the Principal to look up
355
* @param clientDigest Digest which has been submitted by the client
356
* @param nonce Unique (or supposedly unique) token which has been used
358
* @param realm Realm name
359
* @param md5a2 Second MD5 digest used to calculate the digest :
360
* MD5(Method + ":" + uri)
362
public Principal authenticate(String username, String clientDigest,
363
String nonce, String nc, String cnonce,
364
String qop, String realm,
367
String md5a1 = getDigest(username, realm);
370
String serverDigestValue;
372
serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2;
374
serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" +
375
cnonce + ":" + qop + ":" + md5a2;
378
byte[] valueBytes = null;
379
if(getDigestEncoding() == null) {
380
valueBytes = serverDigestValue.getBytes();
383
valueBytes = serverDigestValue.getBytes(getDigestEncoding());
384
} catch (UnsupportedEncodingException uee) {
385
log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
386
throw new IllegalArgumentException(uee.getMessage());
390
String serverDigest = null;
392
synchronized(md5Helper) {
393
serverDigest = md5Encoder.encode(md5Helper.digest(valueBytes));
396
if (log.isDebugEnabled()) {
397
log.debug("Digest : " + clientDigest + " Username:" + username
398
+ " ClientSigest:" + clientDigest + " nonce:" + nonce
399
+ " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop
400
+ " realm:" + realm + "md5a2:" + md5a2
401
+ " Server digest:" + serverDigest);
404
if (serverDigest.equals(clientDigest))
405
return getPrincipal(username);
413
* Return the Principal associated with the specified chain of X509
414
* client certificates. If there is none, return <code>null</code>.
416
* @param certs Array of client certificates, with the first one in
417
* the array being the certificate of the client itself.
419
public Principal authenticate(X509Certificate certs[]) {
421
if ((certs == null) || (certs.length < 1))
424
// Check the validity of each certificate in the chain
425
if (log.isDebugEnabled())
426
log.debug("Authenticating client certificate chain");
428
for (int i = 0; i < certs.length; i++) {
429
if (log.isDebugEnabled())
430
log.debug(" Checking validity for '" +
431
certs[i].getSubjectDN().getName() + "'");
433
certs[i].checkValidity();
434
} catch (Exception e) {
435
if (log.isDebugEnabled())
436
log.debug(" Validity exception", e);
442
// Check the existence of the client Principal in our database
443
return (getPrincipal(certs[0]));
449
* Execute a periodic task, such as reloading, etc. This method will be
450
* invoked inside the classloading context of this container. Unexpected
451
* throwables will be caught and logged.
453
public void backgroundProcess() {
458
* Return the SecurityConstraints configured to guard the request URI for
459
* this request, or <code>null</code> if there is no such constraint.
461
* @param request Request we are processing
462
* @param context Context the Request is mapped to
464
public SecurityConstraint [] findSecurityConstraints(Request request,
467
ArrayList<SecurityConstraint> results = null;
468
// Are there any defined security constraints?
469
SecurityConstraint constraints[] = context.findConstraints();
470
if ((constraints == null) || (constraints.length == 0)) {
471
if (log.isDebugEnabled())
472
log.debug(" No applicable constraints defined");
476
// Check each defined security constraint
477
String uri = request.getRequestPathMB().toString();
478
// Bug47080 - in rare cases this may be null
479
// Mapper treats as '/' do the same to prevent NPE
484
String method = request.getMethod();
486
boolean found = false;
487
for (i = 0; i < constraints.length; i++) {
488
SecurityCollection [] collection = constraints[i].findCollections();
490
// If collection is null, continue to avoid an NPE
491
// See Bugzilla 30624
492
if ( collection == null) {
496
if (log.isDebugEnabled()) {
497
log.debug(" Checking constraint '" + constraints[i] +
498
"' against " + method + " " + uri + " --> " +
499
constraints[i].included(uri, method));
502
for(int j=0; j < collection.length; j++){
503
String [] patterns = collection[j].findPatterns();
505
// If patterns is null, continue to avoid an NPE
506
// See Bugzilla 30624
507
if ( patterns == null) {
511
for(int k=0; k < patterns.length; k++) {
512
if(uri.equals(patterns[k])) {
514
if(collection[j].findMethod(method)) {
515
if(results == null) {
516
results = new ArrayList<SecurityConstraint>();
518
results.add(constraints[i]);
526
return resultsToArray(results);
531
for (i = 0; i < constraints.length; i++) {
532
SecurityCollection [] collection = constraints[i].findCollections();
534
// If collection is null, continue to avoid an NPE
535
// See Bugzilla 30624
536
if ( collection == null) {
540
if (log.isDebugEnabled()) {
541
log.debug(" Checking constraint '" + constraints[i] +
542
"' against " + method + " " + uri + " --> " +
543
constraints[i].included(uri, method));
546
for(int j=0; j < collection.length; j++){
547
String [] patterns = collection[j].findPatterns();
549
// If patterns is null, continue to avoid an NPE
550
// See Bugzilla 30624
551
if ( patterns == null) {
555
boolean matched = false;
557
for(int k=0; k < patterns.length; k++) {
558
String pattern = patterns[k];
559
if(pattern.startsWith("/") && pattern.endsWith("/*") &&
560
pattern.length() >= longest) {
562
if(pattern.length() == 2) {
564
length = pattern.length();
565
} else if(pattern.regionMatches(0,uri,0,
566
pattern.length()-1) ||
567
(pattern.length()-2 == uri.length() &&
568
pattern.regionMatches(0,uri,0,
569
pattern.length()-2))) {
571
length = pattern.length();
577
if(length > longest) {
578
if(results != null) {
583
if(collection[j].findMethod(method)) {
584
if(results == null) {
585
results = new ArrayList<SecurityConstraint>();
587
results.add(constraints[i]);
594
return resultsToArray(results);
597
for (i = 0; i < constraints.length; i++) {
598
SecurityCollection [] collection = constraints[i].findCollections();
600
// If collection is null, continue to avoid an NPE
601
// See Bugzilla 30624
602
if ( collection == null) {
606
if (log.isDebugEnabled()) {
607
log.debug(" Checking constraint '" + constraints[i] +
608
"' against " + method + " " + uri + " --> " +
609
constraints[i].included(uri, method));
612
boolean matched = false;
614
for(int j=0; j < collection.length; j++){
615
String [] patterns = collection[j].findPatterns();
617
// If patterns is null, continue to avoid an NPE
618
// See Bugzilla 30624
619
if ( patterns == null) {
623
for(int k=0; k < patterns.length && !matched; k++) {
624
String pattern = patterns[k];
625
if(pattern.startsWith("*.")){
626
int slash = uri.lastIndexOf("/");
627
int dot = uri.lastIndexOf(".");
628
if(slash >= 0 && dot > slash &&
629
dot != uri.length()-1 &&
630
uri.length()-dot == pattern.length()-1) {
631
if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
641
if(collection[pos].findMethod(method)) {
642
if(results == null) {
643
results = new ArrayList<SecurityConstraint>();
645
results.add(constraints[i]);
651
return resultsToArray(results);
654
for (i = 0; i < constraints.length; i++) {
655
SecurityCollection [] collection = constraints[i].findCollections();
657
// If collection is null, continue to avoid an NPE
658
// See Bugzilla 30624
659
if ( collection == null) {
663
if (log.isDebugEnabled()) {
664
log.debug(" Checking constraint '" + constraints[i] +
665
"' against " + method + " " + uri + " --> " +
666
constraints[i].included(uri, method));
669
for(int j=0; j < collection.length; j++){
670
String [] patterns = collection[j].findPatterns();
672
// If patterns is null, continue to avoid an NPE
673
// See Bugzilla 30624
674
if ( patterns == null) {
678
boolean matched = false;
679
for(int k=0; k < patterns.length && !matched; k++) {
680
String pattern = patterns[k];
681
if(pattern.equals("/")){
686
if(results == null) {
687
results = new ArrayList<SecurityConstraint>();
689
results.add(constraints[i]);
694
if(results == null) {
695
// No applicable security constraint was found
696
if (log.isDebugEnabled())
697
log.debug(" No applicable constraint located");
699
return resultsToArray(results);
703
* Convert an ArrayList to a SecurityContraint [].
705
private SecurityConstraint [] resultsToArray(
706
ArrayList<SecurityConstraint> results) {
707
if(results == null) {
710
SecurityConstraint [] array = new SecurityConstraint[results.size()];
711
results.toArray(array);
717
* Perform access control based on the specified authorization constraint.
718
* Return <code>true</code> if this constraint is satisfied and processing
719
* should continue, or <code>false</code> otherwise.
721
* @param request Request we are processing
722
* @param response Response we are creating
723
* @param constraints Security constraint we are enforcing
724
* @param context The Context to which client of this class is attached.
726
* @exception IOException if an input/output error occurs
728
public boolean hasResourcePermission(Request request,
730
SecurityConstraint []constraints,
734
if (constraints == null || constraints.length == 0)
737
// Specifically allow access to the form login and form error pages
738
// and the "j_security_check" action
739
LoginConfig config = context.getLoginConfig();
740
if ((config != null) &&
741
(Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
742
String requestURI = request.getRequestPathMB().toString();
743
String loginPage = config.getLoginPage();
744
if (loginPage.equals(requestURI)) {
745
if (log.isDebugEnabled())
746
log.debug(" Allow access to login page " + loginPage);
749
String errorPage = config.getErrorPage();
750
if (errorPage.equals(requestURI)) {
751
if (log.isDebugEnabled())
752
log.debug(" Allow access to error page " + errorPage);
755
if (requestURI.endsWith(Constants.FORM_ACTION)) {
756
if (log.isDebugEnabled())
757
log.debug(" Allow access to username/password submission");
762
// Which user principal have we already authenticated?
763
Principal principal = request.getPrincipal();
764
boolean status = false;
765
boolean denyfromall = false;
766
for(int i=0; i < constraints.length; i++) {
767
SecurityConstraint constraint = constraints[i];
770
if (constraint.getAllRoles()) {
771
// * means all roles defined in web.xml
772
roles = request.getContext().findSecurityRoles();
774
roles = constraint.findAuthRoles();
778
roles = new String[0];
780
if (log.isDebugEnabled())
781
log.debug(" Checking roles " + principal);
783
if (roles.length == 0 && !constraint.getAllRoles()) {
784
if(constraint.getAuthConstraint()) {
785
if( log.isDebugEnabled() )
786
log.debug("No roles ");
787
status = false; // No listed roles means no access at all
791
if(log.isDebugEnabled())
792
log.debug("Passing all access");
795
} else if (principal == null) {
796
if (log.isDebugEnabled())
797
log.debug(" No user authenticated, cannot grant access");
799
for (int j = 0; j < roles.length; j++) {
800
if (hasRole(principal, roles[j])) {
802
if( log.isDebugEnabled() )
803
log.debug( "Role found: " + roles[j]);
805
else if( log.isDebugEnabled() )
806
log.debug( "No role found: " + roles[j]);
811
if (!denyfromall && allRolesMode != AllRolesMode.STRICT_MODE &&
812
!status && principal != null) {
813
if (log.isDebugEnabled()) {
814
log.debug("Checking for all roles mode: " + allRolesMode);
816
// Check for an all roles(role-name="*")
817
for (int i = 0; i < constraints.length; i++) {
818
SecurityConstraint constraint = constraints[i];
820
// If the all roles mode exists, sets
821
if (constraint.getAllRoles()) {
822
if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
823
if (log.isDebugEnabled()) {
824
log.debug("Granting access for role-name=*, auth-only");
830
// For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
831
roles = request.getContext().findSecurityRoles();
832
if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
833
if (log.isDebugEnabled()) {
834
log.debug("Granting access for role-name=*, strict auth-only");
843
// Return a "Forbidden" message denying access to this resource
846
(HttpServletResponse.SC_FORBIDDEN,
847
sm.getString("realmBase.forbidden"));
855
* Return <code>true</code> if the specified Principal has the specified
856
* security role, within the context of this Realm; otherwise return
857
* <code>false</code>. This method can be overridden by Realm
858
* implementations, but the default is adequate when an instance of
859
* <code>GenericPrincipal</code> is used to represent authenticated
860
* Principals from this Realm.
862
* @param principal Principal for whom the role is to be checked
863
* @param role Security role to be checked
865
public boolean hasRole(Principal principal, String role) {
867
// Should be overriten in JAASRealm - to avoid pretty inefficient conversions
868
if ((principal == null) || (role == null) ||
869
!(principal instanceof GenericPrincipal))
872
GenericPrincipal gp = (GenericPrincipal) principal;
873
if (!(gp.getRealm() == this)) {
874
if(log.isDebugEnabled())
875
log.debug("Different realm " + this + " " + gp.getRealm());// return (false);
877
boolean result = gp.hasRole(role);
878
if (log.isDebugEnabled()) {
879
String name = principal.getName();
881
log.debug(sm.getString("realmBase.hasRoleSuccess", name, role));
883
log.debug(sm.getString("realmBase.hasRoleFailure", name, role));
891
* Enforce any user data constraint required by the security constraint
892
* guarding this request URI. Return <code>true</code> if this constraint
893
* was not violated and processing should continue, or <code>false</code>
894
* if we have created a response already.
896
* @param request Request we are processing
897
* @param response Response we are creating
898
* @param constraints Security constraint being checked
900
* @exception IOException if an input/output error occurs
902
public boolean hasUserDataPermission(Request request,
904
SecurityConstraint []constraints)
907
// Is there a relevant user data constraint?
908
if (constraints == null || constraints.length == 0) {
909
if (log.isDebugEnabled())
910
log.debug(" No applicable security constraint defined");
913
for(int i=0; i < constraints.length; i++) {
914
SecurityConstraint constraint = constraints[i];
915
String userConstraint = constraint.getUserConstraint();
916
if (userConstraint == null) {
917
if (log.isDebugEnabled())
918
log.debug(" No applicable user data constraint defined");
921
if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
922
if (log.isDebugEnabled())
923
log.debug(" User data constraint has no restrictions");
928
// Validate the request against the user data constraint
929
if (request.getRequest().isSecure()) {
930
if (log.isDebugEnabled())
931
log.debug(" User data constraint already satisfied");
934
// Initialize variables we need to determine the appropriate action
935
int redirectPort = request.getConnector().getRedirectPort();
937
// Is redirecting disabled?
938
if (redirectPort <= 0) {
939
if (log.isDebugEnabled())
940
log.debug(" SSL redirect is disabled");
942
(HttpServletResponse.SC_FORBIDDEN,
943
request.getRequestURI());
947
// Redirect to the corresponding SSL port
948
StringBuffer file = new StringBuffer();
949
String protocol = "https";
950
String host = request.getServerName();
952
file.append(protocol).append("://").append(host);
954
if(redirectPort != 443) {
955
file.append(":").append(redirectPort);
958
file.append(request.getRequestURI());
959
String requestedSessionId = request.getRequestedSessionId();
960
if ((requestedSessionId != null) &&
961
request.isRequestedSessionIdFromURL()) {
963
file.append(Globals.SESSION_PARAMETER_NAME);
965
file.append(requestedSessionId);
967
String queryString = request.getQueryString();
968
if (queryString != null) {
970
file.append(queryString);
972
if (log.isDebugEnabled())
973
log.debug(" Redirecting to " + file.toString());
974
response.sendRedirect(file.toString());
981
* Remove a property change listener from this component.
983
* @param listener The listener to remove
985
public void removePropertyChangeListener(PropertyChangeListener listener) {
987
support.removePropertyChangeListener(listener);
992
// ------------------------------------------------------ Lifecycle Methods
996
* Add a lifecycle event listener to this component.
998
* @param listener The listener to add
1000
public void addLifecycleListener(LifecycleListener listener) {
1002
lifecycle.addLifecycleListener(listener);
1008
* Get the lifecycle listeners associated with this lifecycle. If this
1009
* Lifecycle has no listeners registered, a zero-length array is returned.
1011
public LifecycleListener[] findLifecycleListeners() {
1013
return lifecycle.findLifecycleListeners();
1019
* Remove a lifecycle event listener from this component.
1021
* @param listener The listener to remove
1023
public void removeLifecycleListener(LifecycleListener listener) {
1025
lifecycle.removeLifecycleListener(listener);
1030
* Prepare for the beginning of active use of the public methods of this
1031
* component. This method should be called before any of the public
1032
* methods of this component are utilized. It should also send a
1033
* LifecycleEvent of type START_EVENT to any registered listeners.
1035
* @exception LifecycleException if this component detects a fatal error
1036
* that prevents this component from being used
1038
public void start() throws LifecycleException {
1040
// Validate and update our current component state
1042
if(log.isInfoEnabled())
1043
log.info(sm.getString("realmBase.alreadyStarted"));
1046
if( !initialized ) {
1049
lifecycle.fireLifecycleEvent(START_EVENT, null);
1052
// Create a MessageDigest instance for credentials, if desired
1053
if (digest != null) {
1055
md = MessageDigest.getInstance(digest);
1056
} catch (NoSuchAlgorithmException e) {
1057
throw new LifecycleException
1058
(sm.getString("realmBase.algorithm", digest), e);
1066
* Gracefully terminate the active use of the public methods of this
1067
* component. This method should be the last one called on a given
1068
* instance of this component. It should also send a LifecycleEvent
1069
* of type STOP_EVENT to any registered listeners.
1071
* @exception LifecycleException if this component detects a fatal error
1072
* that needs to be reported
1075
throws LifecycleException {
1077
// Validate and update our current component state
1079
if(log.isInfoEnabled())
1080
log.info(sm.getString("realmBase.notStarted"));
1083
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
1086
// Clean up allocated resources
1093
public void destroy() {
1095
// unregister this realm
1096
if ( oname!=null ) {
1098
Registry.getRegistry(null, null).unregisterComponent(oname);
1099
if(log.isDebugEnabled())
1100
log.debug( "unregistering realm " + oname );
1101
} catch( Exception ex ) {
1102
log.error( "Can't unregister realm " + oname, ex);
1108
// ------------------------------------------------------ Protected Methods
1112
* Digest the password using the specified algorithm and
1113
* convert the result to a corresponding hexadecimal string.
1114
* If exception, the plain credentials string is returned.
1116
* @param credentials Password or other credentials to use in
1117
* authenticating this username
1119
protected String digest(String credentials) {
1121
// If no MessageDigest instance is specified, return unchanged
1122
if (hasMessageDigest() == false)
1123
return (credentials);
1125
// Digest the user credentials and return as hexadecimal
1126
synchronized (this) {
1130
byte[] bytes = null;
1131
if(getDigestEncoding() == null) {
1132
bytes = credentials.getBytes();
1135
bytes = credentials.getBytes(getDigestEncoding());
1136
} catch (UnsupportedEncodingException uee) {
1137
log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
1138
throw new IllegalArgumentException(uee.getMessage());
1143
return (HexUtils.convert(md.digest()));
1144
} catch (Exception e) {
1145
log.error(sm.getString("realmBase.digest"), e);
1146
return (credentials);
1152
protected boolean hasMessageDigest() {
1153
return !(md == null);
1157
* Return the digest associated with given principal's user name.
1159
protected String getDigest(String username, String realmName) {
1160
if (md5Helper == null) {
1162
md5Helper = MessageDigest.getInstance("MD5");
1163
} catch (NoSuchAlgorithmException e) {
1164
log.error("Couldn't get MD5 digest: ", e);
1165
throw new IllegalStateException(e.getMessage());
1169
if (hasMessageDigest()) {
1170
// Use pre-generated digest
1171
return getPassword(username);
1174
String digestValue = username + ":" + realmName + ":"
1175
+ getPassword(username);
1177
byte[] valueBytes = null;
1178
if(getDigestEncoding() == null) {
1179
valueBytes = digestValue.getBytes();
1182
valueBytes = digestValue.getBytes(getDigestEncoding());
1183
} catch (UnsupportedEncodingException uee) {
1184
log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
1185
throw new IllegalArgumentException(uee.getMessage());
1189
byte[] digest = null;
1191
synchronized(md5Helper) {
1192
digest = md5Helper.digest(valueBytes);
1195
return md5Encoder.encode(digest);
1200
* Return a short name for this Realm implementation, for use in
1203
protected abstract String getName();
1207
* Return the password associated with the given principal's user name.
1209
protected abstract String getPassword(String username);
1213
* Return the Principal associated with the given certificate.
1215
protected Principal getPrincipal(X509Certificate usercert) {
1216
return(getPrincipal(usercert.getSubjectDN().getName()));
1221
* Return the Principal associated with the given user name.
1223
protected abstract Principal getPrincipal(String username);
1226
// --------------------------------------------------------- Static Methods
1230
* Digest password using the algorithm specified and
1231
* convert the result to a corresponding hex string.
1232
* If exception, the plain credentials string is returned
1234
* @param credentials Password or other credentials to use in
1235
* authenticating this username
1236
* @param algorithm Algorithm used to do the digest
1237
* @param encoding Character encoding of the string to digest
1239
public final static String Digest(String credentials, String algorithm,
1243
// Obtain a new message digest with "digest" encryption
1245
(MessageDigest) MessageDigest.getInstance(algorithm).clone();
1247
// encode the credentials
1248
// Should use the digestEncoding, but that's not a static field
1249
if (encoding == null) {
1250
md.update(credentials.getBytes());
1252
md.update(credentials.getBytes(encoding));
1255
// Digest the credentials and return as hexadecimal
1256
return (HexUtils.convert(md.digest()));
1257
} catch(Exception ex) {
1266
* Digest password using the algorithm specified and
1267
* convert the result to a corresponding hex string.
1268
* If exception, the plain credentials string is returned
1270
public static void main(String args[]) {
1272
String encoding = null;
1273
int firstCredentialArg = 2;
1275
if (args.length > 4 && args[2].equalsIgnoreCase("-e")) {
1277
firstCredentialArg = 4;
1280
if(args.length > firstCredentialArg && args[0].equalsIgnoreCase("-a")) {
1281
for(int i=firstCredentialArg; i < args.length ; i++){
1282
System.out.print(args[i]+":");
1283
System.out.println(Digest(args[i], args[1], encoding));
1287
("Usage: RealmBase -a <algorithm> [-e <encoding>] <credentials>");
1293
// -------------------- JMX and Registration --------------------
1294
protected String type;
1295
protected String domain;
1296
protected String host;
1297
protected String path;
1298
protected String realmPath = "/realm0";
1299
protected ObjectName oname;
1300
protected ObjectName controller;
1301
protected MBeanServer mserver;
1303
public ObjectName getController() {
1307
public void setController(ObjectName controller) {
1308
this.controller = controller;
1311
public ObjectName getObjectName() {
1315
public String getDomain() {
1319
public String getType() {
1323
public String getRealmPath() {
1327
public void setRealmPath(String theRealmPath) {
1328
realmPath = theRealmPath;
1331
public ObjectName preRegister(MBeanServer server,
1332
ObjectName name) throws Exception {
1335
domain=name.getDomain();
1337
type=name.getKeyProperty("type");
1338
host=name.getKeyProperty("host");
1339
path=name.getKeyProperty("path");
1344
public void postRegister(Boolean registrationDone) {
1347
public void preDeregister() throws Exception {
1350
public void postDeregister() {
1353
protected boolean initialized=false;
1355
public void init() {
1356
if( initialized && container != null ) return;
1358
// We want logger as soon as possible
1359
if (container != null) {
1360
this.containerLog = container.getLogger();
1364
if( container== null ) {
1365
ObjectName parent=null;
1366
// Register with the parent
1368
if( host == null ) {
1370
parent=new ObjectName(domain +":type=Engine");
1371
} else if( path==null ) {
1372
parent=new ObjectName(domain +
1373
":type=Host,host=" + host);
1375
parent=new ObjectName(domain +":j2eeType=WebModule,name=//" +
1378
if( mserver.isRegistered(parent )) {
1379
if(log.isDebugEnabled())
1380
log.debug("Register with " + parent);
1381
mserver.setAttribute(parent, new Attribute("realm", this));
1383
} catch (Exception e) {
1384
log.error("Parent not available yet: " + parent);
1391
ContainerBase cb=(ContainerBase)container;
1392
oname=new ObjectName(cb.getDomain()+":type=Realm" +
1393
getRealmSuffix() + cb.getContainerSuffix());
1394
Registry.getRegistry(null, null).registerComponent(this, oname, null );
1395
if(log.isDebugEnabled())
1396
log.debug("Register Realm "+oname);
1397
} catch (Throwable e) {
1398
log.error( "Can't register " + oname, e);
1405
protected String getRealmSuffix() {
1406
return ",realmPath=" + getRealmPath();
1410
protected static class AllRolesMode {
1412
private String name;
1413
/** Use the strict servlet spec interpretation which requires that the user
1414
* have one of the web-app/security-role/role-name
1416
public static final AllRolesMode STRICT_MODE = new AllRolesMode("strict");
1417
/** Allow any authenticated user
1419
public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode("authOnly");
1420
/** Allow any authenticated user only if there are no web-app/security-roles
1422
public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode("strictAuthOnly");
1424
static AllRolesMode toMode(String name)
1427
if( name.equalsIgnoreCase(STRICT_MODE.name) )
1429
else if( name.equalsIgnoreCase(AUTH_ONLY_MODE.name) )
1430
mode = AUTH_ONLY_MODE;
1431
else if( name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name) )
1432
mode = STRICT_AUTH_ONLY_MODE;
1434
throw new IllegalStateException("Unknown mode, must be one of: strict, authOnly, strictAuthOnly");
1438
private AllRolesMode(String name)
1443
public boolean equals(Object o)
1445
boolean equals = false;
1446
if( o instanceof AllRolesMode )
1448
AllRolesMode mode = (AllRolesMode) o;
1449
equals = name.equals(mode.name);
1453
public int hashCode()
1455
return name.hashCode();
1457
public String toString()