In this setup we will use the office-wide LDAP authentication server to authenticate the client using HTTP basic authentication (digest will not work).
First of all discover the structure of LDAP directory. You need to know the LDAP server URL, DN prefix for user and group search:
$ ldapsearch -H ldap://ldap.company.org:389/ -D uid=myuid,ou=people,dc=company,dc=org -w <mypassword> -b 'uid=myuid,ou=people,dc=company,dc=org' dn: uid=myuid, ou=people, dc=company, dc=org objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson uid: myuid cn: Dmitry Katsubo sn: Katsubo givenname: Dmitry displayname: Dmitry Katsubo ...
For group (role) search we will use the following filter:
$ ldapsearch -H ldap://ldap.company.org:389/ -D uid=myuid,ou=people,dc=company,dc=org -w <mypassword> -b 'ou=groups,dc=company,dc=org' -s sub '(member=uid=myuid,ou=people,dc=company,dc=org)' dn: cn=subdiv5587, ou=groups, dc=company, dc=org objectclass: top objectclass: groupOfNames cn: subdiv5587 description: Subdevision 55.87 member: uid=myuid, ou=people, dc=company, dc=org ... other members ... dn: cn=div55, ou=groups, dc=company, dc=org objectclass: top objectclass: groupOfNames cn: div55 description: Devision 55 member: uid=myuid, ou=people, dc=company, dc=org ... other members ...
Based on the discovered information we define in Tomcat's server.xml
the following realm1):
<Server ...> ... <Realm className="org.apache.catalina.realm.CombinedRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> <Realm className="org.apache.catalina.realm.JNDIRealm" connectionURL="ldap://ldap.company.org:389/" alternateURL="ldap://ldap-replica.company.org:389/" userPattern="uid={0},ou=people,dc=company,dc=org" roleBase="ou=groups,dc=company,dc=org" roleName="cn" roleSearch="(member={0})" roleSubtree="false" /> </Realm> </Server>
This combined realm uses the local user database (e.g. you want to define local Tomcat admins or application deployers) as well as the LDAP directory to authenticate. The last one applies authentication-by-binding mechanism. If want client to check the password, use userSearch
+ userSubtree
JNDIRealm
properties.
Now you can say in application's WEB-INF/web.xml
:
<web-app version="2.4" ...> ... <security-constraint> <web-resource-collection> <web-resource-name>All Pages</web-resource-name> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>dir2711</role-name> </auth-constraint> </security-constraint> <security-role id="SecurityRole_0"> <description>The role that is assigned to all members of Subdevision 55.87.</description> <role-name>subdiv5587</role-name> </security-role> <login-config> <auth-method>BASIC</auth-method> <realm-name>MyApplication</realm-name> </login-config> </web-app>
You can define several <url-pattern>
s and several <http-method>
s in <web-resource-collection>
. The <role-name>
from <auth-constraint>
should be also described by <security-role>
.
<auth-method>
represents basic HTTP/1.1 authentication methods:
<form-login-config>
element2).In the code one can use HttpServletRequest#getRemoteUser() and HttpServletRequest#isUserInRole() methods:
package org.company.application.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class MyWebController { private static final Log logger = LogFactory.getLog(MyWebController.class); @RequestMapping(value = "/processFile", method = RequestMethod.POST) public void processFile(HttpServletRequest request, HttpServletResponse response) { logger.debug("User:" + request.getRemoteUser() + ", is in directorate:" + request.isUserInRole("subdiv5587")); ... } }
Let's test the above declared POST request handler:
$ wget -S -nv -t 1 --post-file myfile.txt -O - http://myuid:mypasword@service-id.company.org:8080/application/processFile HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 WWW-Authenticate: Basic realm="MyApplication" Content-Type: text/html;charset=utf-8 Content-Length: 954 Date: Thu, 05 Aug 2010 16:00:40 GMT Connection: keep-alive HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/plain;charset=US-ASCII Content-Length: 3999 Date: Thu, 05 Aug 2010 16:01:09 GMT Connection: keep-alive
In a log file:
DEBUG [org.apache.catalina.authenticator.AuthenticatorBase] Security checking request POST /application/processFile DEBUG [org.apache.catalina.realm.RealmBase] Checking constraint 'SecurityConstraint[All Pages]' against POST /processFile --> true DEBUG [org.apache.catalina.authenticator.AuthenticatorBase] Calling hasUserDataPermission() DEBUG [org.apache.catalina.realm.RealmBase] User data constraint has no restrictions DEBUG [org.apache.catalina.authenticator.AuthenticatorBase] Calling authenticate() DEBUG [org.apache.catalina.realm.CombinedRealm] Attempting to authenticate user "myuid" with realm "org.apache.catalina.realm.UserDatabaseRealm/1.0" DEBUG [org.apache.catalina.realm.CombinedRealm] Cannot find message associated with key combinedRealm.authFail DEBUG [org.apache.catalina.realm.CombinedRealm] Attempting to authenticate user "myuid" with realm "org.apache.catalina.realm.JNDIRealm/1.0" DEBUG [org.apache.catalina.core.ContainerBase.[Catalina]] Found role: div55 DEBUG [org.apache.catalina.core.ContainerBase.[Catalina]] Found role: subdiv5587 DEBUG [org.apache.catalina.realm.CombinedRealm] Authenticated user "myuid" with realm "org.apache.catalina.realm.JNDIRealm/1.0" DEBUG [org.apache.catalina.authenticator.AuthenticatorBase] Authenticated 'myuid' with type 'BASIC' DEBUG [org.apache.catalina.authenticator.AuthenticatorBase] Calling accessControl() DEBUG [org.apache.catalina.realm.RealmBase] Checking roles GenericPrincipal[myuid(div55,subdiv5587)] DEBUG [org.apache.catalina.realm.RealmBase] Different realm org.apache.catalina.realm.CombinedRealm@1d32e45 org.apache.catalina.realm.JNDIRealm@12f1bf0 DEBUG [org.apache.catalina.realm.RealmBase] Username myuid has role subdiv5587 DEBUG [org.apache.catalina.realm.RealmBase] Role found: subdiv5587 DEBUG [org.apache.catalina.authenticator.AuthenticatorBase] Successfully passed all security constraints DEBUG [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping] Mapping [/processFile] to handler 'org.company.application.web.MyWebController@24f4eb' DEBUG [org.company.application.web.MyWebController] User:myuid, is in directorate:true
For more information see Realm Configuration HOWTO.
General installation steps:
/etc/krb5.conf
(or use the system-wide one, defined by Linux administrator): [libdefaults] default_realm = REALM.COMPANY.ORG default_tkt_enctypes = des-cbc-md5 des-cbc-crc default_tgs_enctypes = des-cbc-md5 des-cbc-crc kdc_timesync = 0 kdc_default_options = 0x40000010 clockskew = 300 check_delegate = 0 ccache_type = 3 kdc_timeout = 60 [domain_realm] company.org = REALM.COMPANY.ORG .company.org = REALM.COMPANY.ORG [realms] REALM.COMPANY.ORG = { admin_server = server1.company.org kdc = server1.company.org kdc = server2.company.org }
Installation of SF SPNEGO module
For Jetty see How to configure Jetty to use SPNEGO/NEGOTIATE authentication.
For local services development, follow SPNEGO Tomcat instructions:
java.net.SocketException
SEVERE: StandardServer.await: create[8005]: java.net.SocketException: Invalid argument at java.net.PlainSocketImpl.socketBind(Native Method) at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:365) at java.net.ServerSocket.bind(ServerSocket.java:319) at java.net.ServerSocket.<init>(ServerSocket.java:185) at org.apache.catalina.core.StandardServer.await(StandardServer.java:373) at org.apache.catalina.startup.Catalina.await(Catalina.java:662) at org.apache.catalina.startup.Catalina.start(Catalina.java:614) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)
-Djava.net.preferIPv4Stack=true
(see Java Networking Properties) or set net.ipv6.bindv6only
kernel system variable to zero (see this conversion):# This sysctl sets the default value of the IPV6_V6ONLY socket option. # # When disabled, IPv6 sockets will also be able to send and receive IPv4 # traffic with addresses in the form ::ffff:192.0.2.1 and daemons listening # on IPv6 sockets will also accept IPv4 connections. # # When IPV6_V6ONLY is enabled, daemons interested in both IPv4 and IPv6 # connections must open two listening sockets. # This is the default behaviour of all modern operating systems. net.ipv6.bindv6only = 0
For correct UTF-8 URL handling set <Connector ... URIEncoding="UTF-8" />
option in server.xml
or use <Connector ... useBodyEncodingForURI="true" />
.
The contents (request body) is not correctly represented because by default the character encoding of HttpServletRequest
is set to ISO-8859-1 (unless is provided in browser request). Spring has a special CharacterEncodingFilter
filter that fixes this (see sample here).
Unable to add the resource at [/…] to the cache for web application [/app] because there was insufficient free space available after evicting expired cache entries - consider increasing the maximum size of the cache
<Context> ... <Resources cachingAllowed="true" cacheMaxSize="100000" />
META-INF/context.xml
<Context path="/myapp"> </Context>
however this behaviour has changed in Tomcat 6 (see Context Container, bug#46713 and Tomcat's context path for a webapp not working):
path
attribute must only be used when statically defining a Context inserver.xml
. In all other circumstances, the path will be inferred from the filenames used for either the .xml context file or thedocBase
.
For deployment to Tomcat 6 you should use tomcat-maven-plugin
(that deploys via Tomcat Manager):
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <configuration> <!-- Server credentials are stored in settings.xml (should match with ones in /etc/tomcat6/tomcat-users.xml): --> <server>tomcat-server-id</server> <url>${tomcat.server.url}/manager</url> <!-- Deployment context: --> <path>${context.path}</path> </configuration> </plugin>
Tomcat has build-in memory leak protections, one of which does not allow to automatically image providers from web application in JVM-wide ImageIO registry. This is done by JreMemoryLeakPreventionListener which initializes ImageIO (call to any static method forces static registry to be initialized).
As a workaround appContextProtection
has to be disabled for JreMemoryLeakPreventionListener
:
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" appContextProtection="false"/>
however this is not a complete solution as if application is redeployed again and again, the references to image providers will still be kept together with classloader and all classes it has loaded. This will cause PermGen to run out of memory and under certain conditions may cause ClassCastException when using ImageIO.
One solution is provided in IIOProviderContextListener.java
(see How to resolve OutOfMemoryError with ImageIO plugins as the cause), and basically consists of the following steps:
ImageIO.scanForPlugins()
before using any ImageIO API (servlet initialization).
Another solution is to put jai_imageio.jar
+ jai_codec.jar
into server-wide lib/
folder.
References:
log4j
logging for Tomcat? /usr/share/tomcat6/lib/log4j.properties
log4j.rootLogger=INFO, R log4j.appender.R=org.apache.log4j.RollingFileAppender log4j.appender.R.File=${catalina.base}/logs/tomcat.log log4j.appender.R.MaxFileSize=50MB log4j.appender.R.MaxBackupIndex=5 log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%d{dd.MM HH:mm:ss} %-5p [%c] %m%n # For a more detailed Catalina localhost log, uncomment below: #log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost]=DEBUG #log4j.logger.org.apache.catalina.core=DEBUG #log4j.logger.org.apache.catalina.session=DEBUG
/etc/tomcat6/logging.properties
file you will not be able to see the debug output done via java.util.logging
. So either keep it, or remove/rename and continue with steps below.
Also you might be interested in redirecting the java.util.logging
to Log4j completely so you don't need to dig two log files for errors as everything will be in one file. For that you need to do further steps to one mentioned above:
apache-jul-log4j-bridge.jar
, log4j-1.2.15.jar
and log4j.properties
directory location into Tomcat boot classpath: /usr/share/tomcat6/bin/catalina.sh
--- catalina.sh.orig 2010-06-14 21:21:36.000000000 +0200 +++ catalina.sh 2010-08-09 13:06:38.000000000 +0200 @@ -164,12 +164,17 @@ # Add tomcat-juli.jar and bootstrap.jar to classpath # tomcat-juli.jar can be over-ridden per instance if [ ! -z "$CLASSPATH" ] ; then - CLASSPATH="$CLASSPATH": + CLASSPATH="$CLASSPATH:" fi -if [ "$CATALINA_BASE" != "$CATALINA_HOME" ] && [ -r "$CATALINA_BASE/bin/tomcat-juli.jar" ] ; then - CLASSPATH="$CLASSPATH""$CATALINA_BASE"/bin/tomcat-juli.jar:"$CATALINA_HOME"/bin/bootstrap.jar -else - CLASSPATH="$CLASSPATH""$CATALINA_HOME"/bin/bootstrap.jar + +CLASSPATH="$CLASSPATH""$CATALINA_HOME"/bin/bootstrap.jar + +if [ "$CATALINA_BASE" != "$CATALINA_HOME" ] && [ -r "$CATALINA_HOME/bin/tomcat-juli.jar" ] ; then + CLASSPATH="$CLASSPATH:$CATALINA_HOME/bin/tomcat-juli.jar" +fi + +if [ -r "$CATALINA_HOME/lib/apache-jul-log4j-bridge.jar" -a -r "$CATALINA_HOME/lib/log4j-1.2.15.jar" ] ; then + CLASSPATH="$CLASSPATH:$CATALINA_HOME/lib/apache-jul-log4j-bridge.jar:$CATALINA_HOME/lib/log4j-1.2.15.jar:$CATALINA_HOME/lib" fi if [ -z "$CATALINA_OUT" ] ; then
java.util.logging.manager
property either via LOGGING_MANAGER
or JAVA_OPTS
(check your startup scripts and catalina.sh
about how to do it correctly): LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.logging.julbridge.JULBridgeLogManager"
catalina.sh
you need to fix /etc/init.d/tomcat6
(or /usr/sbin/dtomcat6
): --- /usr/bin/dtomcat6.orig 2011-02-22 23:20:16.000000000 +0100 +++ /usr/bin/dtomcat6 2011-12-12 10:29:12.000000000 +0100 @@ -24,6 +25,9 @@ fi CLASSPATH="${CLASSPATH}:${CATALINA_HOME}/bin/bootstrap.jar" CLASSPATH="${CLASSPATH}:${CATALINA_HOME}/bin/tomcat-juli.jar" +CLASSPATH="${CLASSPATH}:${CATALINA_HOME}/lib/apache-jul-log4j-bridge.jar" +CLASSPATH="${CLASSPATH}:${CATALINA_HOME}/lib/log4j-1.2.15.jar" +CLASSPATH="${CLASSPATH}:${CATALINA_HOME}/lib" CLASSPATH="${CLASSPATH}:$(build-classpath commons-daemon 2>/dev/null)" if [ "$1" = "start" ]; then @@ -34,7 +38,7 @@ -Djava.endorsed.dirs="$JAVA_ENDORSED_DIRS" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ -Djava.util.logging.config.file="${CATALINA_BASE}/conf/logging.properties" \ - -Djava.util.logging.manager="org.apache.juli.ClassLoaderLogManager" \ + -Djava.util.logging.manager="org.apache.logging.julbridge.JULBridgeLogManager" \ org.apache.catalina.startup.Bootstrap start \ >> ${CATALINA_BASE}/logs/catalina.out 2>&1 & if [ ! -z "$CATALINA_PID" ]; then
Finally, checklist:
/usr/share/tomcat6/bin
contains:catalina.sh
(patched for classpath)tomcat-juli.jar
(from extras)/usr/share/tomcat6/lib
contains:tomcat-juli-adapters.jar
(from extras)log4j.properties
(newly created)log4j-1.2.15.jar
apache-jul-log4j-bridge.jar
/etc/default/tomcat6
is fixed for LOGGING_MANAGER
.and you are ready to restart Tomcat and enjoy the result.
For slf4j logging, check here.
mod_jk
could not be multiplexed into one HTTP/2 stream, see this post. To enable multiplexing JBoss 5.x / Apache Tomcat 9 is required. There could be different options where to offload TLS:
ProxyPass "/path" "h2://jboss.server"
+ <UpgradeProtocol className=“org.apache.coyote.http2.Http2Protocol” />
inside TLS enabled <Connector …>
element).ProxyPass "/path" "h2c://jboss.server"
+ <UpgradeProtocol className=“org.apache.coyote.http2.Http2Protocol” />
inside <Connector …>
element).See also:
XmiRoleMappingLoginModule
module, that is responsible for mapping users to security roles using the mapping file application-bnd.xmi