Sunday, April 5. 2020
JavaEE 8 Security API in Wildfly
Unexpectedly I am going to write a continuation of the previous entry about JASPI. The past week I needed to work with the new Java EE Security API Specification (JSR 375) and, because it is based on JASPI, the new specification is somewhat related to the previous post. There are several good articles over the internet about the new javaee security framework, for example Jakarta EE 8 Security API by baeldung or the overview done by DZone. Today's entry is just going to use the JSR 375 specification with a Wildfly 19 server.
Following the previous guides about the new API, a little sample application is going to be developed. The framework lets you configure the application login just defining a ApplicationScoped bean. The bean defines the authentication mechanism and the storage to use.
package es.rickyepoderi;
import javax.enterprise.context.ApplicationScoped;
import javax.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition;
import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
import javax.security.enterprise.identitystore.LdapIdentityStoreDefinition;
@FormAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue(
loginPage = "/login.html",
errorPage = "/login-error.html"
))
@LdapIdentityStoreDefinition(
url="ldap://localhost:1389",
bindDn="cn=Directory Manager",
bindDnPassword="XXXX",
callerSearchBase="ou=People,dc=sample,dc=com",
groupSearchBase="ou=Groups,dc=sample,dc=com",
groupNameAttribute="cn",
groupMemberAttribute="uniqueMember"
)
@ApplicationScoped
public class ApplicationConfig {
}
So the application is going to use a FORM login (an HTML page will be shown to the user to login) and the user info are going to be retrieved from an LDAP server. The API gives more options for the mechanism (BasicAuthenticationMechanismDefinition and CustomFormAuthenticationMechanismDefinition) and the storage (DatabaseIdentityStoreDefinition or a custom IdentityStore). With the bean done, a HelloServlet is introduced.
package es.rickyepoderi;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet({"/hello"})
@ServletSecurity(@HttpConstraint(rolesAllowed={"static-group1"}))
public class HelloWorldServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try (PrintWriter writer = response.getWriter()) {
response.setContentType("text/plain;charset=UTF-8");
writer.append("Hello ")
.append(request.getUserPrincipal() == null? "World" : request.getUserPrincipal().getName())
.append("!");
}
}
}
The Servlet is annotated to request a role (static-group1) which will be a group of the user in the LDAP repository. In my humble opinion the security API needs to be extended to apply these constraints in other places, right now only servlets can be annotated and, if I am not wrong, this is quite short (why not annotate other generic URLs that can be JSP, JSF, jax-rs,...). Adding a general place (in the bean for example?) to include constraints for generic URLs would be awesome. I have to say that I am still surprised about this, if I missed something please add a comment in the bottom section.
The final step when using wildfly is defining the jaspitest security domain. Why? Because the JSR 375 works with JASPI and, therefore, the application should be configured to use a JASPI capable security domain. The default security domain other configured by wildfly is not JASPI. In a common application the jboss-web.xml should be placed setting the module to use.
<jboss-web>
<security-domain>jaspitest</security-domain>
</jboss-web>
And that is all. The application should work in wildfly 19 for example. Just download, install it and deploy the application. You can download the maven project for the sample app from here.
wget https://download.jboss.org/wildfly/19.0.0.Final/wildfly-19.0.0.Final.zip
unzip wildfly-19.0.0.Final.zip
cd wildfly-19.0.0.Final/bin
./add-user.sh -u admin -p XXXX
./standalone.sh
./jboss-cli.sh --connect
deploy /path/to/javaee8sec/target/javaee8sec.war
But what if I do not want to specify the jboss-web.xml in the application, in the end this is not part of the standard. Well, the solution is quite simple, you can just change the default security domain for the web/undertow to jaspitest.
./jboss-cli.sh --connect
/subsystem=undertow:write-attribute(name=default-security-domain, value=jaspitest)
/:reload
The application now will work without the jboss-web.xml, because the jaspitest security domain is used by default by the apps that do not specify one. But what happens with elytron, in the previous entry we saw that the new security subsystem supports JASPI too, so, how can we configure the server to use it instead of the old security domain? Just like it was shown in the old entry, an undertow/elytron domain should be configured with JASPI enabled but not integrated. Here it is an example to configure elytron globally and enable JASPI for the security API.
cd wildfly-19.0.0.Final
bin/jboss-cli.sh --file=docs/examples/enable-elytron.cli
cd bin
./standalone.sh
./jboss-cli.sh --connect
/subsystem=undertow/application-security-domain=other:write-attribute(name=integrated-jaspi, value=false)
/subsystem=undertow:write-attribute(name=default-security-domain, value=other)
/:reload
The provided enable-elytron.cli is first used to completely configure elytron security (now everything in wildfly uses elytron instead of the old security system). Finally the default other domain is changed to set integrated-jaspi to false. Remember that this property makes that JASPI alone is enough to authenticate users, if false the user is also located in the associated realm inside elytron and for the security API that needs to be avoided.
As a summary wildfly 19 is JavaEE 8 certified and, therefore, the new security API is available. In the entry a little sample application was deployed to authenticate users against an LDAP server. The final points were used to avoid the inclusion of the jboss-web.xml in the application and configure the server to automatically use a domain that is JASPI enabled, using both, the old jaspitest domain and the new elytron susbsystem.
Best regards!
Sunday, March 15. 2020
JASPI authentication in elytron
Today's entry is dedicated to Java Authentication Service Provider Interface for Containers (JASPIC or just JASPI) in the wildfly server. Since version 15 the new elytron security provider allows JASPI configuration for applications. This post is going to explain how to move from the old security configuration (which also provided JASPI) to the new one.
JASPI or JASPIC is a standard JavaEE API to authenticate users into the container. In general a ServerAuthModule should be implemented and the method validateRequest gets the user data from the request and validates it against the users and roles repository (LDAP, database,...). For the entry I am going to use a silly example. My module is just a BASIC authentication, it reads the header Authorization to retrieve the username and password, and validates the data against a pre-configured list of users. In JASPI the initialize method receives a map of options, and that map will be used to pass the list of usernames, passwords and roles managed by my sample. Each map entry will have the following format:
"username" => "password[###role1,role2,...]"
It is just a toy module but enough to exemplify how to use it in the old and the new security system. Here you can download the jaspi-module maven project which generates the final library (jaspi-module-1.0.jar). Let's start the configuration in wildfly 18.
The first step is adding the module into the server.
module add --name=es.rickyepoderi.jaspi --resources=/path/to/jaspi-module-1.0.jar --dependencies=javax.servlet.api,javax.security.auth.message.api
Now configure the JASPI in a security domain.
/subsystem=security/security-domain=jaspi-module:add /subsystem=security/security-domain=jaspi-module/authentication=jaspi:add(auth-modules=[{code=es.rickyepoderi.jaspi.ExampleServerAuthModule, module=es.rickyepoderi.jaspi, flag=required, module-options={"user1" => "password1###user,role1", "user2" => "password2###user", "user3" => "password3"}}])
The security domain called jaspi-module adds my ServerAuthModule and initializes it with three users (user1, user2 and user3), and the first two are assigned to the role user while the third one is not.
Now it is the time to deploy an application. A hello-world-jaxrs app was developed that is just a hello world but secured with the previous module. The hello endpoint says hello to the logged user. For that, the standard web.xml is used.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <security-constraint> <web-resource-collection> <web-resource-name>All resources</web-resource-name> <description>Protects all resources</description> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>user</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <security-role> <description>Role required to log in to the Application</description> <role-name>user</role-name> </security-role> <login-config> <auth-method>BASIC</auth-method> </login-config> </web-app>
All the pages are protected and requires the user role (the one assigned to the first two users but not to user3). And finally the custom jboss-web.xml uses the jaspi-module security domain configured in the previous point.
<jboss-web> <security-domain>jaspi-module</security-domain> </jboss-web>
The application is deployed inside wildfly and can be tested using curl: the hello endpoint is not allowed for anonymous users; user1 can access and receives the hello; but user3 is forbidden (this user lacks the valid role).
curl http://localhost:8080/hello-world-jaxrs/api/hello <html><head><title>Error</title></head><body>Unauthorized</body></html> curl --anyauth --user user1:password1 http://localhost:8080/hello-world-jaxrs/api/hello Hello user1! curl --anyauth --user user3:password3 http://localhost:8080/hello-world-jaxrs/api/hello <html><head><title>Error</title></head><body>Forbidden</body></html>
So it is working. The old configuration for JASPI in wildfly is simple. A security domain was the only way of using this standard until wildfly 15.
Time to use the new JASPI elytron way. The old security domain is removed and a new jaspi-configuration is needed inside the elytron section.
/subsystem=security/security-domain=jaspi-module:remove /subsystem=elytron/jaspi-configuration=simple-configuration:add(layer=HttpServlet, application-context="default-host /hello-world-jaxrs", server-auth-modules=[{class-name=es.rickyepoderi.jaspi.ExampleServerAuthModule, flag=REQUIRED, module=es.rickyepoderi.jaspi, options={"user1" => "password1###user,role1", "user2" => "password2###user", "user3" => "password3"}}])
The new elytron configuration assigns JASPI to the application context, but the rest is more or less the same than in the old way (class and module, and the same configuration to define our three users).
Then JASPI needs a security domain to be created for the web layer undertow.
/subsystem=undertow/application-security-domain=jaspi-module:add(security-domain=ApplicationDomain, enable-jaspi=true, integrated-jaspi=false)
The domain uses the default ApplicationDomain, but please note that the option integrated-jaspi is set to false. This means that the JASPI module is enough, an ad hoc identity will be created independently of the domain realm. The default value true uses the realm in the ApplicationDomain to locate user and roles, so, besides the JASPI authentication, it also requires the user to be present in the realm.
Finally the same curl commands of step 4 can be executed. They should return the same output.
That is all for today. The new elytron security provides the old fashioned JASPI authentication since wildfly 15. The idea is more or less the same than in the old sub-system and, in general, the steps to configure it are very similar. Both configurations work, so, if you are using JASPI for your applications, better start using the new elytron way. The two little projects used in the entry (the JASPI module and the hello application) can be downloaded using the links provided before.
Best regards!
Saturday, February 29. 2020
Testing Spring Boot (Part II)
In the previous entry a simple Spring Boot application was presented. The main goal in the PoC was using Spring Boot but trying to limit the development to JavaEE technologies (servlet and jax-rs) plus some common security and web service libraries (keycloak to secure the endpoints and swagger to document them). The first part left some steps still missing, I wanted to check how easy was moving from one implementation to another (from the default tomcat servlet container to undertow, and from jersey to resteasy jax-rs implementation) and, finally, run the same application inside a wildfly server (check war and jar deployment). This second part of the series deals with those two missing points.
Moving from tomcat to undertow
Spring Boot by default uses tomcat as the embedded web servlet container. So, in the first part, tomcat was used because nothing was modified and the default dependencies were in place. In order to include undertow the tomcat dependency should be excluded from the jersey starter and the undertow one should be added. So now the application only used the following two spring starters.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
And it works, the startup logs show that undertow is used at port 8080. So moving from tomcat to undertow is perfect, just switching the starters, undertow is added and tomcat is excluded. Really nice!
Moving from jersey to resteasy
Now let's change the jax-rs implementation from the oracle/eclipse implementation (jersey) to the JBoss one (resteasy). As both are standard jax-rs implementations the change should have been transparent but, as it was commented in the previous entry, the jersey starter is using the private ResourceConfig configuration class. So that will not work for sure with the new implementation. First the jersey started should be substituted with the reasteasy one (jersey was commented out). Only the resteasy and the undertow starter remains and, inside the former, tomcat should also be excluded to maintain undertow working.
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring-boot-starter</artifactId>
<version>4.4.0.Final</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
Direct compilation doesn't work, because the ResourceConfig is not available in the resteasy implementation. Now the standard Application class should be used. In this case the jersey starter is doing the weird thing, the standard way should also be allowed, but it is not. We need a little change in the code, only in the application definition.
package es.rickyepoderi.springboot.rest;
import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContextBuilder;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import io.swagger.v3.oas.integration.OpenApiConfigurationException;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
//import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;
@ApplicationPath("api")
@Component
public class RestApplication extends Application { // extends Application { //extends ResourceConfig {
@Context
private ServletConfig servletConfig;
// it doesn't work with standard Application, why?
//https://stackoverflow.com/questions/47237180/how-to-register-resource-with-javax-ws-rs-core-applicaiton-in-spring-boot-starte
public RestApplication() {
//register(HelloEndpoint.class);
//register(OpenApiResource.class);
//register(RolesAuthorizationFilter.class);
try {
new JaxrsOpenApiContextBuilder()
.servletConfig(servletConfig)
.application(this)
.openApiConfiguration(new SwaggerConfiguration()
.openAPI(new OpenAPI()
.info(new Info()
.title("Hello App")
.description("Sample Spring Boot Hello App")
.version("0.0.1-SNAPSHOT")))
.prettyPrint(true)
.resourcePackages(Collections.singleton(HelloEndpoint.class.getPackage().getName())))
.buildContext(true);
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e);
}
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(HelloEndpoint.class);
classes.add(OpenApiResource.class);
classes.add(RolesAuthorizationFilter.class);
return classes;
}
}
The Application class is used and the resources to integrate are returned in the getClasses method (instead of the register that jersey uses). The jersey configuration was maintained as comments to change between both implementations in my tests. After this change the application can be compiled and everything else works, our little app is running with undertow and resteasy.
Moving to a wildfly server
The last step was moving from the jar bundle that has been used until now to a war deployment inside wildfly. The first change is switching the packaging type in the pom.xml.
<packaging>war</packaging>
If the application is just generated like this the war is very big, with tons of libraries in the WEB-INF/lib directory that are not needed (they are already available inside the wildfly server). A lot of time was spent checking the maven dependencies (mvn dependency:tree -Dverbose and so on and so forth), playing with exclusions and scopes (provided scope is very useful here). I finished with the following pom.xml. Some points to comment:
The resteasy-spring-boot-starter and spring-boot-starter-undertow are defined as provided in order to have them for compilation and avoid the inclusion of their libraries. But, in spite of that, a lot of artifacts should be explicitly excluded in both starters.
As the previous two starters are defined as provided, the default spring-boot-starter-web is added (this way default spring libraries are incorporated to the war). Again a lot of exclusions are listed in order to remove duplicated or not needed artifacts.
The dependency reactive-streams is also needed in the war bundle. It seems that at deploy time wildfly checks for CDI annotations (that some spring classes seem to have) and processing them several NoClassDefFoundError exceptions are thrown (org/reactivestreams/Subscriber or org/reactivestreams/Processor).
<dependency> <groupId>org.reactivestreams</groupId> <artifactId>reactive-streams</artifactId> <version>1.0.3</version> </dependency>
The keycloak filter depends in bouncy castle and http-client, those libraries are inside wildfly but are private modules (they are not exposed by default to applications). I decided to add a dependency for both in the MANIFEST file and exclude those two artifacts.
<plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.3</version> <configuration> <archive> <manifestEntries> <Dependencies>org.apache.httpcomponents.core,org.bouncycastle</Dependencies> </manifestEntries> </archive> </configuration> </plugin>
Finally, with the final pom.xml, the resulting war file is about 14MB in size (the initial file was more than 30MB!). I suppose that you can strip it even more. But the summary is that a detailed review of the dependencies is needed to change from the standalone jar to the war bundle. This part is painful and necessary to create a correct war for wildfly with a decent size.
Video
The same steps done in the previous video are shown but in a different order. This time the war is deployed inside a wildfly 18. First the bundle is generated and deployed in the server. Then the context is accessed and keycloak intercepts it and requests the user to login. Same as before, user ricky can execute the endpoint but admin cannot. After logout the simple servlet in the application continues working, the endpoint returns 401 if not logged in and swagger keeps giving a correct definition for it. So everything is working again, but this time inside the wildfly JavaEE container.
In my humble opinion Spring Boot gives a good support for interchanging implementations (tomcat or undertow, jersey or resteasy, standalone or container deployment). There are several snags, jersey using a private initialization class (this issue is something very specific to this starter) or the mess with dependencies when deploying inside the container. But I think it is almost impossible to do it seamlessly, some minor changes are going to be needed for sure. I like the Spring Boot idea. That is all for today. The final project deployed inside wildfly can be downloaded from here.
Regards!
Comments