In my application I use form-based authentication with a LDAP-Realm. For Authorization I use a database. As I understand this works as follows
App --> (user, pass) --> LDAP
<-- OK, user exists --
--> ask for security roles for 'user' --> JACC / Database
<-- Administrator --
Can I hook into the process where my application calls ask for security roles for 'user'
?
Background:
LDAP says: Okay, 'user' is authentified
Database : give me all roles where username = user
And now I want to customize the Database query: give me all roles where username = 'user' AND some more attributes
Is this somehow possible?
TL;DR: Have a look at this and that for a sample solution.
What you ask for depends on the degree of flexibility offered by the vendor-specific feature you use; your product may or may not allow you to extend the behaviour of that/these LoginModule
/ Realm
/ IdentityStore
/ whatever-it's/they're-called proprietary class(es), or maybe even to just type an SQL query into some administrative UI's input field. The bottom line is that it's non-standard functionality.
On the standard Java EE side of the spectrum there are the JASPIC (user / message authentication) and JACC (authorization) SPIs. Both can be used to retrieve security-related information pertaining to your users from some external store. What JASPIC cannot do is change a user's roles after authentication; that is, for the duration of an authenticated request 1 the user's roles are fixed. JASPIC can also not attach meaning to those roles; for it they're just plain String
s that the AS will in some proprietary manner derive group Principal
s from. JACC, on the other hand can do those things, as it establishes a "rulebase" (think Policy
) which precisely associates roles, principals and Permission
s and can be queried on each and every user-system interaction. JACC can also override or alter the interpretation of the Java EE security constraints expressed via deployment descriptors and annotations.
I will include a JASPIC-based solution in this post and disregard JACC for the most part, because:
- You may not need the additional flexibility that JACC offers.
- Employing a custom JACC provider solution requires quite a bit of work and is still not 100% standard due to AS-specific group-to-role-mapping.
- I'm unaware of an open-source project using a custom JACC provider for accomplishing something meaningful; Java EE Full Profile-implementing AS's being the sole exception, as they are mandated to implement the spec and sometimes use their JACC providers internally too (as e.g. in the case of GlassFish).
- There's still a lot of confusion surrounding JASPIC (many developers are unaware it even exists) which is the simplest one of the two specs. It's imho reasonable to first make JASPIC known and "approachable" to more people before moving on to covering JACC.
- While there are now more than a mere few great examples on JASPIC online, as are projects implementing actual authentication via JASPIC providers, -correct me if I'm mistaken- I haven't yet found a complete JASPIC example here on SO.
Some remarks on what follows:
- No warranty / use at own risk / don't sue me if your app / AS / system explodes or gets hacked by alien / etc.
- Please, try to disregard those spots where optimization, better design or other improvements would in your opinion have been essential. Yes you could admittedly reuse DB / LDAP connections; validate usernames inserted into LDAP search filters; care more about thread safety; use TLS; return a 400 for non-well-formed XML payloads... . Addressing those concerns is outside the scope of the provided solution. Excuse me?? You wonder where the unit tests are???! Sorry, never heard of the term! :)
- Two SAMs concretely performing authentication and retrieval of groups (roles) are provided: a "standalone", which accomplishes both tasks on its own, and a "delegating" one, which, as its name suggests, demonstrates how a SAM can delegate actual authentication work to a JAAS
LoginModule
(LM), by employing the JASPIC LoginModule Bridge Profile. The latter SAM requires further configuration / adaptation, both at the AS and at the source level per se, except if you are using GlassFish. An accompanying example JAAS login.conf
entry is provided.
- The provider classes were tested against GlassFish 4.1. They strive to remain spec-compliant and should therefore really work on your AS too (except for the second SAM, obviously), provided that your product implements the Full Java EE (6, preferably 7) Profile. I'm sorry if it doesn't; and no, I won't test on your AS.
- You can avoid studying / using the
AuthConfigProvider
and ServerAuthConfig
implementations, but will then have to register the actual SAM with your product's AuthConfigFactory
in a proprietary way (via a vendor-specific foo-web.xml
and/or further use of deployment / administrative tools). You will additionally not have the SAM implementing the ServerAuthContext
interface and will have to load the accompanying Properties
from within the SAM. Your AS will then instantiate the missing classes for you, possibly reusing a "global" AuthConfigProvider
and/or ServerAuthConfig
it has pre-configured for all applications and message layers. Note that, depending on whether it reuses its instantiated ServerAuthConfig
and ServerAuthContext
across requests (hardly ever the case especially with the latter), the lifecycle of your SAM may be affected.
- Group-to-role mapping is not covered as it's container-specific.
- I included comments wherever I felt like doing so. Not all are (completely) useless. Aided by the spec, the code should be comprehensible --but feel free to ask if something's troubling you. Apologies for the overlength post.
- Paths are absolute from the root of a standard Maven project. Once you've adapted the properties and/or authentication / group retrieval methods in the SAMs, you can build all files as a WAR and deploy the latter on your AS as-is to test it. The sole dependency is that of (
provided
) javaee-api
7.0 (plus your JDBC driver, unless already present on the AS class path).
- I had to move the code to a Gist due to SO's post length constraint.
- The
ServletContextListener
registering the AuthConfigProvider
. Save as /<project>/src/main/java/org/my/foosoft/authn/BigJaspicFactoryRegistrar.java
.
- The
AuthConfigProvider
. Save as /<project>/src/main/java/org/my/foosoft/authn/BigJaspicFactory.java
.
- The
ServerAuthConfig
. Save as /<project>/src/main/java/org/my/foosoft/authn/LittleJaspicServerFactory.java
.
- The base dual
ServerAuthContext
- ServerAuthModule
implementation helper class. This is part 1/3 of the actual answer. Save as /<project>/src/main/java/org/my/foosoft/authn/HttpServletSam.java
.
- The standalone SAM implementation. Actual answer, part 2/3. Save as
/<project>/src/main/java/org/my/foosoft/authn/StandaloneLdapSam.java
.
- The JAAS LM-delegating SAM. Actual answer, part 3/3. Save as
/<project>/src/main/java/org/my/foosoft/authn/JaasDelegatingLdapSam.java
.
- Convenience exception type. Save as
/<project>/src/main/java/org/my/foosoft/authn/JaspicMischief.java
.
- The accompanying
Properties
. Adapt them to your needs if you'd like to test the actual authentication code verbatim. Save as /<project>/src/main/resources/org/my/foosoft/authn/jaspic-provider.properties
.
- Accompanying sample
login.conf
snippet for (6); consult vendor's documentation for actual file system location.
/<project>/src/main/java/org/my/foosoft/presentation/UserUtils.java
(optional - for demonstration purposes: JSF backing bean)
/<project>/src/main/webapp/index.xhtml
(optional - for demonstration purposes: unprotected index page)
/<project>/src/main/webapp/login.xhtml
(optional - for demonstration purposes: login page)
/<project>/src/main/webapp/restricted/info.xhtml
(optional - for demonstration purposes: protected index page for users in role access_restricted_pages
)
/<project>/src/main/webapp/WEB-INF/web.xml
(optional - for demonstration purposes and for the sake of completness: web module DD)
Further reading:
- JSR-196 (JASPIC) Specification
- Arjan Tijms's JASPIC ZEEF page
- JSR-115 (JACC) Specification
1 JASPIC is very a generic SPI, in theory able to authenticate JMS, SAML-over-SOAP and any other kind of message, when plugged into a capable message processing runtime. Even its predominantly used Servlet Container Profile does not overly constrain it.
JASPIC's low-level, flexible nature entails unawareness of protocol-specific functionality, such as the HTTP session. Consequently, ServerAuthContext
s / SAMs are triggered by the runtime to perform authentication on every request.
The spec however makes a provision about this potential shortcoming by allowing SAMs to request initiation of a container authentication session by the runtime, via a MessageInfo
Callback Property. When asked to authenticate subsequent requests of the same client, SAMs can avoid repeating the entire process, by asking the runtime to reuse the previously established AS authentication session, hence the user identity (caller and/or group Principal
(s)). That is accomplished via execution of the "do-nothing-/leave-authentication-state-as-is-protocol" shown in the HttpServletSam
of the sample code.
It should lastly be noted that neither the JASPIC nor the Servlet spec clearly defines what a container authentication session is. For a SAM-authenticated user I would, for all practical purposes, consider the AS authentication session to be the equivalent of the HTTP session, as long as a) authentication pertains to a single application context and b) the SAM, as explained above, signals reuse of the AS authentication session on each request.
You could customize it using Spring Security, authenticating against LDAP and configuring the authentication manager/authentication-provider:
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:jdbc-user-service data-source-ref="dataSource"
authorities-by-username-query="select AUTHORITIES.AUTHORITIES_AUTH from AUTHORITIES, USERS where AUTHORITIES.AUTHORITIES_USER_ID = USERS.USERS_ID and ..."/>