Saturday, May 5. 2018
Fallback authentication in Wildfly with elytron
This entry is to summarize a quick test I did the previous week. I already knew that wildfly manages the idea of a fallback authentication or, saying it differently, it can handle more than one authentication mechanism. A second authentication method is useful when using with an automatic or non-interactive mechanism like SPNEGO (kerberos, windows built-in Single Sign-On) or CLIENT-CERT (authentication based in a client X509 certificate). This way, if the first method is not available (for whatever reason), the user can at least login to the application using a typical username and password. The old wildfly security module provides this feature combining the ability of undertow of managing more than one authentication mechanism and the stacking of login modules (JAAS idea), but I had not seen any example for the new elytron subsystem to do the same, so I decided to investigate and do a quick test.
Finally I could make it work, but, always I work with elytron, my feeling is that the new security framework is extremely cumbersome and barely polished. My idea was simple, the application used in the second entry of the certificate security series was going to be extended to have a fallback BASIC authentication. This way the users are authenticated using an X509 certificate or a typical username and password following the BASIC web standard. The usernames and passwords are going to be retrieved from a simple properties file (but you can use any other realm, like LDAP or JDBC).
The full steps to configure the elytron subsystem are going to be shown (although some initial parts, like adding https protocol or generating the java key-stores, are omitted, please go to the previous entry if you are interested in the full configuration).
In order to authenticate the certificates we need a key-store that contains the valid certificates for users:
/subsystem=elytron/key-store=users:add(type=jks, relative-to=jboss.server.config.dir, path=users.jks, credential-reference={clear-text=Kiosko_00})
With the previous store the realm is created:
/subsystem=elytron/key-store-realm=users-realm:add(key-store=users)
Now a second realm is created but this one is based on properties files (I used plain passwords to not complicate the solution).
/subsystem=elytron/properties-realm=properties-realm:add(users-properties={path=example-users.properties, relative-to=jboss.server.config.dir, plain-text=true}, groups-properties={path=example-roles.properties, relative-to=jboss.server.config.dir}, groups-attribute=Roles})
A role mapper is going to be used to assign a Users role to any logged user:
/subsystem=elytron/constant-role-mapper=users-roles:add(roles=[Users])
Finally the domain is created, but the difference with the previous entry is that now the domain admits both realms but defaults to the properties one.
/subsystem=elytron/security-domain=certificate-domain:add(role-mapper=users-roles, realms=[{realm => users-realm}, {realm => properties-realm}], default-realm=properties-realm, permission-mapper=default-permission-mapper)
And the tricky part comes here, elytron lets us map several realms using a concept called realm-mapper. My idea is mapping the certificate or the properties realm depending the mechanism used, so I need two mappers and a constant type is enough to accomplish what I want.
/subsystem=elytron/constant-realm-mapper=constant-properties-realm:add(realm-name=properties-realm) /subsystem=elytron/constant-realm-mapper=constant-users-realm:add(realm-name=users-realm)
Finally the http factory is created, but it is going to have two mechanisms, CLIENT_CERT which will use the certificate mapper and in turn the certificate realm, and BASIC that will use the properties mapper and realm.
/subsystem=elytron/http-authentication-factory=certificate-auth-fact:add(http-server-mechanism-factory=global, security-domain=certificate-domain, mechanism-configurations=[{mechanism-name => CLIENT_CERT, realm-mapper => constant-users-realm}, {mechanism-name => BASIC, realm-mapper => constant-properties-realm}]}])
Finally the factory is assigned to the web/undertow subsystem to be able to assign it to the application.
/subsystem=undertow/application-security-domain=certificate-sec-domain:add(http-authentication-factory=certificate-auth-fact)
The final step is just modifying the web.xml of the previous application and assigning both mechanisms in the login-config section:
<login-config> <auth-method>CLIENT-CERT,BASIC</auth-method> <realm-name>certificate-sec-domain</realm-name> </login-config>
And that is all. Here it is the video that shows that the certificate login is still functional but now a simple curl with BASIC can also be used to call to the web-service method. First the command is executed without any authentication and a 401 is returned with the proper WWW-Authenticate response header set to BASIC. Then a username/password is sent and the XML is returned as expected.
Just one last comment about elytron. In the previous entry a x500-attribute-principal-decoder was configured to parse the certificate but failed, here it gave an error when the properties realm was used (it tries to decode a X509 certificate and the principal is not a certificate, so an exception is thrown). Therefore I removed it, but, why is the principal decoder at domain level and not at realm level? Does it make sense? The domain can have more than one realm, and those realms can be different so, or the decoder should handle different types of principals, or it should be assigned to the realm. An unexpected issue for sure.
As a summary, elytron can handle a fallback authentication mechanism. The entry takes advantage of the realm mappers (that can be used for a lot of more things I guess) and assigns two of them to the http factory to use a certificate or a properties realm depending the mechanism that the user used for login. This way both methods work, first CLIENT-CERT is tried but if the connection does not present a client certificate a BASIC negotiation is started. Each mechanism triggers the login with a different realm and present the user to the application. I did not try kerberos/spnego but I think it would be the same (or even simpler cos the final realm can be the same). The maven project is not attached to the entry because it is the same to the one you can find in the previous entry, except the modification in the web.xml commented before.
Best regards!
Comments