- Jakarta EE 8
- Wildfly 21
- Java 11
Using Java EE Security, I'm trying custom form authentication in a simple application.
These are the relevant files (the description of the problem is below the files):
CustomFormAuthenticationConfig.java
package br.dev.authentication.view;
import javax.enterprise.context.ApplicationScoped;
import javax.faces.annotation.FacesConfig;
import javax.security.enterprise.authentication.mechanism.http.CustomFormAuthenticationMechanismDefinition;
import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
@CustomFormAuthenticationMechanismDefinition(
loginToContinue = @LoginToContinue(
loginPage = "/login.xhtml",
useForwardToLogin = false,
errorPage = ""
)
)
@FacesConfig
@ApplicationScoped
public class CustomFormAuthenticationConfig
{
}
UserAuthenticator.java
package br.dev.authentication.view;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.security.enterprise.credential.Credential;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.IdentityStore;
@ApplicationScoped
public class UserAuthenticator implements IdentityStore
{
@Override
public CredentialValidationResult validate(Credential credencial)
{
var userCredentials = (UsernamePasswordCredential) credencial;
var userName = userCredentials.getCaller();
var password = userCredentials.getPasswordAsString();
if (userName.equals("1") && password.equals("1"))
{
return new CredentialValidationResult(userName, Set.of("USER"));
}
else
{
return CredentialValidationResult.INVALID_RESULT;
}
}
}
login.xhtml
<ui:composition template="/templates/layout.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:au="http://dev.br/authentication">
<ui:define name="title">
Login
</ui:define>
<ui:define name="content">
<au:errors />
<div id="fields">
<h:outputLabel value="User name:" for="userName" />
<h:inputText id="userName" value="#{login.userName}" />
<h:outputLabel value="Password:" for="password" />
<h:inputSecret id="password" value="#{login.password}" />
<h:commandButton value="Enter" action="#{login.authenticateUser}" />
</div>
</ui:define>
</ui:composition>
Login.java
package br.dev.authentication.view;
import java.io.IOException;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.security.enterprise.AuthenticationStatus;
import javax.security.enterprise.SecurityContext;
import javax.security.enterprise.authentication.mechanism.http.AuthenticationParameters;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
@RequestScoped
@Named
public class Login
{
private String _userName;
private String _password;
@Inject
private FacesContext _facesContext;
@Inject
private ExternalContext _externalContext;
@Inject
private SecurityContext _securityContext;
@NotBlank(message = "User name is required.")
public String getUserName()
{
return _userName;
}
public void setUserName(String userName)
{
_userName = userName;
}
@NotBlank(message = "Password is required.")
public String getPassword()
{
return _password;
}
public void setPassword(String password)
{
_password = password;
}
public void authenticateUser() throws IOException
{
// After a successful login (username and password correct),
// executeUserAuthentication() returns AuthenticationStatus.SEND_CONTINUE,
// and not AuthenticationStatus.SUCCESS.
// Why?
// As a result, the code in the AuthenticationStatus.SUCCESS branch above is not executed.
AuthenticationStatus result = executeUserAuthentication();
if (result == AuthenticationStatus.SUCCESS)
{
_externalContext.redirect(_externalContext.getRequestContextPath() + "/start.xhtml");
}
else if (result == AuthenticationStatus.SEND_CONTINUE)
{
_facesContext.responseComplete();
}
else if (result == AuthenticationStatus.SEND_FAILURE)
{
_facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Invalid user name and/or password.", null));
}
}
private AuthenticationStatus executeUserAuthentication()
{
return _securityContext.authenticate(
(HttpServletRequest) _externalContext.getRequest(),
(HttpServletResponse) _externalContext.getResponse(),
AuthenticationParameters.withParams().credential(
new UsernamePasswordCredential(_userName, _password))
);
}
}
The problem is that, after a successful login (user name and password correct), as you saw in the comments in Login class above, the method executeUserAuthentication() returns AuthenticationStatus.SEND_CONTINUE instead of AuthenticationStatus.SUCCESS. Below is an image of the application running in debug mode in the moment of execution to show this:
As a result, the browser address bar is not updated with the real url (start.xhtml) because the AuthenticationStatus.SUCCESS branch in the code above was not executed:
I thought I was doing something wrong, but then I decided to clone this simple application from @ArjanTijms that uses the same authentication logic:
https://github.com/rieckpil/blog-tutorials/tree/master/jsf-simple-login-with-java-ee-security-api
His application is explained in this post:
https://rieckpil.de/howto-simple-form-based-authentication-for-jsf-2-3-with-java-ee-8-security-api/
And the results were the same of my application: the AuthenticationStatus.SEND_CONTINUE branch was executed instead of the AuthenticationStatus.SUCCESS branch:
No updated address bar too:
So, what's going on here? Is this a problem in the applications, Wildfly, is this correct behaviour (if so, what's the use of the AuthenticationStatus.SUCCESS enum, when will it be used?)? I just want to be able to execute the redirect by myself.
UPDATE 1
For your information, after the executeUserAuthentication() the user is already authenticated: ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getUserPrincipal() is not null.
Some other files in the application:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<display-name>autenticacao-visao</display-name>
<!-- ========== JSF ========== -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
<!-- ========== Security ========== -->
<security-constraint>
<web-resource-collection>
<web-resource-name>restrict</web-resource-name>
<url-pattern>/app/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>USER</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>allowed</web-resource-name>
<url-pattern>/app/resources/*</url-pattern>
</web-resource-collection>
</security-constraint>
<security-role>
<role-name>USER</role-name>
</security-role>
<!-- ========== Resources ========== -->
<context-param>
<param-name>dev.br.RESOURCES</param-name>
<param-value>resources</param-value>
</context-param>
<!-- ========== Start page ========== -->
<welcome-file-list>
<welcome-file>app/start.xhtml</welcome-file>
</welcome-file-list>
</web-app>
jboss-app.xml
<?xml version="1.0" encoding="UTF-8"?>
<jboss-app>
<security-domain>jaspitest</security-domain>
</jboss-app>
start.xhtml
<ui:composition template="/templates/layout.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<ui:define name="title">
Início
</ui:define>
<ui:define name="content">
#{start.loggedInMessage}
<br />
<h:commandButton value="Execute" />
</ui:define>
</ui:composition>
Start.java
package br.dev.authentication.view;
import java.security.Principal;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
@RequestScoped
@Named
public class Start
{
@Inject
private ExternalContext _externalContext;
public String getLoggedInMessage()
{
Principal user = ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getUserPrincipal();
if (user != null)
{
return "Logged in: " + user.getName();
}
else
{
return "Not logged in";
}
}
}
UPDATE 2
After lots of tests, I noticed (at least in my application) that sometimes SecurityContext.authenticate() returns AuthenticationStatus.SEND_CONTINUE and sometimes returns AuthenticationStatus.SUCCESS. Until now, I haven't figured out the reason why.
So, for the time being, I decided to solve the problem with a hack for the cases when AuthenticationStatus.SEND_CONTINUE would be returned. So, basically, what I'm doing is redirecting to the start page manually. The hack is below:
web.xml (changed the welcome page to redirectortostart.xhtml)
<welcome-file-list>
<welcome-file>app/redirectortostart.xhtml</welcome-file>
</welcome-file-list>
redirectortostart.xhtml
<ui:composition template="/templates/layout.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<ui:define name="metadados">
<f:event type="preRenderView" listener="#{redirectorToStart.goToStart}" />
</ui:define>
</ui:composition>
RedirectorToStart.java
package br.dev.authentication.view;
import java.io.IOException;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.inject.Inject;
import javax.inject.Named;
@RequestScoped
@Named
public class RedirectorToStart
{
@Inject
private ExternalContext _externalContext;
public void goToStart() throws IOException
{
_externalContext.redirect(_externalContext.getRequestContextPath() + "/app/start.xhtml");
}
}
It's not elegant, but for the time being it solves my problem. Hope someday some of you have a clue about what really could be happening with my application and could give a hint to me.




