Hook on process where container asks for roles of

2019-03-04 13:13发布

问题:

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?

回答1:

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 Strings that the AS will in some proprietary manner derive group Principals from. JACC, on the other hand can do those things, as it establishes a "rulebase" (think Policy) which precisely associates roles, principals and Permissions 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.

  1. The ServletContextListener registering the AuthConfigProvider. Save as /<project>/src/main/java/org/my/foosoft/authn/BigJaspicFactoryRegistrar.java.
  2. The AuthConfigProvider. Save as /<project>/src/main/java/org/my/foosoft/authn/BigJaspicFactory.java.
  3. The ServerAuthConfig. Save as /<project>/src/main/java/org/my/foosoft/authn/LittleJaspicServerFactory.java.
  4. 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.
  5. The standalone SAM implementation. Actual answer, part 2/3. Save as /<project>/src/main/java/org/my/foosoft/authn/StandaloneLdapSam.java.
  6. The JAAS LM-delegating SAM. Actual answer, part 3/3. Save as /<project>/src/main/java/org/my/foosoft/authn/JaasDelegatingLdapSam.java.
  7. Convenience exception type. Save as /<project>/src/main/java/org/my/foosoft/authn/JaspicMischief.java.
  8. 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.
  9. Accompanying sample login.conf snippet for (6); consult vendor's documentation for actual file system location.
  10. /<project>/src/main/java/org/my/foosoft/presentation/UserUtils.java (optional - for demonstration purposes: JSF backing bean)
  11. /<project>/src/main/webapp/index.xhtml (optional - for demonstration purposes: unprotected index page)
  12. /<project>/src/main/webapp/login.xhtml (optional - for demonstration purposes: login page)
  13. /<project>/src/main/webapp/restricted/info.xhtml (optional - for demonstration purposes: protected index page for users in role access_restricted_pages)
  14. /<project>/src/main/webapp/WEB-INF/web.xml (optional - for demonstration purposes and for the sake of completness: web module DD)

Further reading:

  1. JSR-196 (JASPIC) Specification
  2. Arjan Tijms's JASPIC ZEEF page
  3. 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, ServerAuthContexts / 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.



回答2:

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 ..."/>