Authentication is never really easy to get right but it is important. So there are plenty of frameworks out there to facilitate authentication for developers.
The current installment of the authentication system in Wildfly/JEE7 right now is called Elytron which makes using different authentication backends mostly a matter of configuration. This configuration however is quite extensive and consists of several entities due to its flexiblity. Some may even say it is over-engineered…
Therefore I want to provide some kind of a walkthrough of how to get authentication up and running in Wildfly elytron by using a LDAP user store as the backend.
Our aim is to configure the authentication with a LDAP backend, to implement login/logout and to secure our application endpoints using annotations.
Setup
Of course you need to install a relatively modern Wildfly JEE server, I used Wildfly 26. For your credential store and authentication backend you may setup a containerized Samba server, like I showed in a previous blog post.
Configuration of security realms, domains etc.
We have four major components we need to configure to use the elytron security subsystem of Wildfly:
- The security domain defines the realms to use for authentication. That way you can authenticate against several different realms
- The security realms define how to use the identity store and how to map groups to security roles
- The dir-context defines the connection to the identity store – in our case the LDAP server.
- The application security domain associates deployments (aka applications) with a security domain.
So let us put all that together in a sample configuration:
<subsystem xmlns="urn:wildfly:elytron:15.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
...
<security-domains>
<security-domain name="DevLdapDomain" default-realm="AuthRealm" permission-mapper="default-permission-mapper">
<realm name="AuthRealm" role-decoder="groups-to-roles"/>
</security-domain>
</security-domains>
<security-realms>
...
<ldap-realm name="LdapRealm" dir-context="ldap-connection" direct-verification="true">
<identity-mapping rdn-identifier="CN" search-base-dn="CN=Users,DC=ldap,DC=schneide,DC=dev">
<attribute-mapping>
<attribute from="cn" to="Roles" filter="(member={1})" filter-base-dn="CN=Users,DC=ldap,DC=schneide,DC=dev"/>
</attribute-mapping>
</identity-mapping>
</ldap-realm>
<ldap-realm name="OtherLdapRealm" dir-context="ldap-connection" direct-verification="true">
<identity-mapping rdn-identifier="CN" search-base-dn="CN=OtherUsers,DC=ldap,DC=schneide,DC=dev">
<attribute-mapping>
<attribute from="cn" to="Roles" filter="(member={1})" filter-base-dn="CN=auth,DC=ldap,DC=schneide,DC=dev"/>
</attribute-mapping>
</identity-mapping>
</ldap-realm>
<distributed-realm name="AuthRealm" realms="LdapRealm OtherLdapRealm"/>
</security-realms>
<dir-contexts>
<dir-context name="ldap-connection" url="ldap://ldap.schneide.dev:389" principal="CN=Administrator,CN=Users,DC=ldap,DC=schneide,DC=dev">
<credential-reference clear-text="admin123!"/>
</dir-context>
</dir-contexts>
</subsystem>
<subsystem xmlns="urn:jboss:domain:undertow:12.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="DevLdapDomain" statistics-enabled="true">
...
<application-security-domains>
<application-security-domain name="myapp" security-domain="DevLdapDomain"/>
</application-security-domains>
</subsystem>
In the above configuration we have two security realms using the same identity store to allow authenticating users in separate subtrees of our LDAP directory. That way we do not need to search the whole directory and authentication becomes much faster.
Note: You may not need to do something like that if all your users reside in the same subtree.
The example shows a simple, but non-trivial use case that justifies the complexity of the involved entities.
Implementing login functionality using the Framework
Logging users in, using their session and logging them out again is almost trivial after all is set up correctly. Essentially you use HttpServletRequest.login(username, password)
, HttpServletRequest.getSession()
, HttpServletRequest.isUserInRole(role)
and HttpServletRequest.logout()
to manage your authentication needs.
That way you can check for active session and the roles of the current user when handling requests. In addition to the imperative way with isUserInRole()
we can secure endpoints declaratively as shown in the last section.
Declarative access control
In addition to fine grained imperative access control using the methods on HttpServletRequest we can use annotations to secure our endpoints and to make sure that only authenticated users with certain roles may access the endpoint. See the following example:
@WebServlet(urlPatterns = ["/*"], name = "MyApp endpoint")
@ServletSecurity(
HttpConstraint(
transportGuarantee = ServletSecurity.TransportGuarantee.NONE,
rolesAllowed = ["oridnary_user", "super_admin"],
)
)
public class MyAppEndpoint extends HttpServlet {
...
}
To allow unauthenticated access you can use the value
attribute instead of rolesAllowed
in the HttpConstraint
:
@ServletSecurity(
HttpConstraint(
transportGuarantee = ServletSecurity.TransportGuarantee.NONE,
value = ServletSecurity.EmptyRoleSemantic.PERMIT)
)
I hope all of the above helps to setup simple and secure authentication and authorization in Wildfly/JEE.
How does logout work? I have my app configured as above and invalidating the session does nothing, the user can hit the back button and they are able to access the resources.
Invalidating the session seems not to be enough. I suggest you call HttpServletRequest.logout() in the endpoint for the logout-Operation.
Both session.invalidate() and request.logout() have been called and the user can still hit the back button to access the app. Is there a working example available?
HttpSession session = a_request.getSession(false);
if ( session != null ) {
session.invalidate();
}
a_request.logout();