XPages to Java EE, Part 12: Container Authentication
Wed Feb 20 12:24:56 EST 2019
- XPages to Java EE, Part 1: Overview
- XPages to Java EE, Part 2: Terminology
- XPages to Java EE, Part 3: Hello, World
- XPages to Java EE, Part 4: Application Servers
- XPages to Java EE, Part 5: Web Pages
- XPages to Java EE, Part 6: Dependencies
- XPages to Java EE, Part 7: MVC
- XPages to Java EE, Part 8: IDE Server Integration
- XPages to Java EE, Part 9: IDE Features Grab Bag
- XPages to Java EE, Part 10: Data Storage
- XPages to Java EE, Part 11: Mixing MVC and an API
- XPages to Java EE, Part 12: Container Authentication
- XPages to Java EE, Part 13: Why Do This, Anyway?
Coming from Domino, we're both spoiled by the way it handles users and authentication. The server comes with an implicit directory far removed from the app, and so we just use that. Moreover, the HTTP "container" handles authentication for us: we configure whether we want HTTP Basic auth, simple session auth, LTPA, or SAML (I guess) authentication at the server/web site level, also far from the app. Then, in the application, we just set up an ACL in the DB or XPage and reader/author fields on notes and let the server take care of it.
We're also pretty constrained by this, as anyone who has wanted to set up a custom in-app login page, in-app user registry, or even server-level specialized user repository knows. The convenience comes with a cost.
Java EE's Routes
Authentication in Java EE has a complicated history, but the general idea is that there are three main current ways to handle user registries and authentication:
- Container authentication, which is roughly equivalent to Domino. In this case, you configure your server with knowledge about the user registry (say, a static set of users or an LDAP server) and how those users map to "roles" in the JEE sense (more on this in a bit), and then your app just tells the server what parts need login.
- Per-app custom authentication. This is roughly equivalent to doing a special in-app session cookie in a Domino app for authentication, but the hooks provided by Java EE make it easier to manage. You could do this entirely homebrew or use a project like Apache Shiro to do the heavy lifting for you.
- The Java EE Security API 1.0. This is a new standard, introduced in Java EE 8, meant to give apps the fine-grained custom control of the second option while offloading as many particulars as desired to the servlet container - essentially, a "best of both worlds" approach. In practice, I've found it a bit under-documented and fiddly in its current incarnation, but I'll aim to cover it in a future post.
Container Authentication
For this post, I'll cover setting up container authentication, since it's the easiest, is well-documented, and can even serve the "per-app" role if you follow the "one app per server" model that's in vogue for microservice/"cloud native" applications.
For testing and development cases, web servers generally provide a mechanism for writing out a simple user/password list. Open Liberty does this via server.xml and Tomcat/TomEE does it via tomcat-users.xml. However, since we're Domino people, that means we have access to an LDAP server we're already comfortable with running, so we can jump right into a "real" setup.
Configuring Domino for LDAP
This topic is a bit out of the bailiwick of this series, but usually it just means creating an LDAP Internet Site and running load ldap
. Domino's pretty convenient sometimes. I like to test a freshly-configured LDAP server with Apache Directory Studio.
Configuring Liberty
Once you have LDAP running and working, open Liberty's "server.xml" file (dubbed "Server Configuration" in Eclipse's Servers view) and add the ldapRegistry-3.0
feature and an ldapRegistry
element within the top-level server
element, customized for your setup:
<featureManager>
<feature>javaee-8.0</feature>
<feature>localConnector-1.0</feature>
<feature>mpOpenAPI-1.0</feature>
<feature>ldapRegistry-3.0</feature>
</featureManager>
<ldapRegistry host="some.domino.server" port="389" sslEnabled="false"
bindDN="someuser" bindPassword="somepassword"
ldapType="IBM Lotus Domino" baseDN=""/>
There are ways to prevent putting an unencrypted password in the "server.xml" file, but this will do for now.
As a nice bonus, Liberty's IBM provenance means we get built-in knowledge of Domino and so don't have to jump through the common hoops of setting up query filters and whatnot.
The other bit of configuration in this file is to map users and groups to roles. This is an area where Domino and JEE diverge a bit. "Roles" in JEE mean more or less the same thing as in Domino, but they're configured outside of the application. They can be configured per-app within the server configuration, accomplishing the same end result, but there's a different level of indirection. For our users, we'll configure them server-wide. So, add another element as a peer to the ldapRegistry
:
<authorization-roles>
<security-role name="admin">
<group name="LocalDomainAdmins"/>
</security-role>
</authorization-roles>
You can feel free to customize this at will - there's also a user
type and the concept lines up pretty much with what you'd expect.
Configuring the App
The server configuration on its own doesn't yet change anything - we can still visit any page in the app without a login prompt. What we should do now is to clamp down certain aspects. To demonstrate this, we'll add a UI operation for deleting people and restrict it to our freshly-minted "admin" role. Go to the PersonController
class and add the method:
@POST
@Path("{id}/delete")
@RolesAllowed("admin")
@Controller
@Operation(hidden=true)
public String deletePerson(@PathParam("id") String id) {
personRepository.deleteById(id);
return "redirect:people"; //$NON-NLS-1$
}
Note that we're using POST
for this instead of DELETE
as a nod to web browsers' historical limitations. There is a way to intercept and remap incoming requests so that we can use @DELETE
annotations that I may cover later, but for now this should work fine.
Modify the "personList.tag" file within "WEB-INF/tags" in "Deployed Resources" to include a button to point to this action:
<%@tag description="Displays List&kt;model.Person>" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@attribute name="value" required="true" type="java.util.List" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h1>People</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email Address</th>
<th></th>
</tr>
</thead>
<tbody>
<c:forEach items="${pageScope.value}" var="person">
<tr>
<td><c:out value="${person.id}"/></td>
<td><c:out value="${person.name}"/></td>
<td><c:out value="${person.emailAddress}"/></td>
<td>
<form action="${pageContext.request.contextPath}/resources/people/${person.id}/delete" method="POST">
<input type="submit" value="X"/>
</form>
</td>
</tr>
</c:forEach>
</tbody>
</table>
If you visit http://localhost:9091/javaeetutorial/resources/people and click one of the deletion buttons, you should be greeted with an HTTP Basic authentication prompt that should block you until you enter in valid credentials for an admin.
Next Steps
At this point, we've really covered the main aspects that you'd need to know when making the move to Java EE. After this point, I have some ideas for various miscellaneous posts - other authentication options, app fit and finish, JSF, app configuration, deployment, and so forth - but, if you've been following along, I suggest you take the opportunity now to explore for yourself.
It would also be worth your time to look up some other existing educational resources. I hear that Java Brains is quite good on a lot of these topics, and Adam Bien is one of the leading sources for Java EE examples (and is the source of the template we used at the very start). When looking around, be wary of the age of the content: though pretty much anything related to Java EE will still work, there was a big move away from the Bad Old Days in recent versions, particularly EE 8, and so older examples may have you jump through uglier hoops than necessary. There are also whole technologies that are in common use that are not of immediate interest to us, such as Enterprise JavaBeans, so you can learn or not learn about those at will.