问题
- 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.
回答1:
The AuthenticationStatus is used as a return value by primarily the HttpAuthenticationMechanism to indicate the result (status) of the authentication process.
SEND_CONTINUE : The authentication mechanism was called and a multi-step authentication dialog with the caller has been started (for instance, the caller has been redirected to a login page). Simply said authentication is "in progress". Calling application code (if any) should not write to the response when this status is received. Javadocs
How does your web.xml looks like?
have you added the security constraints ?
Example :
<security-constraint>
<web-resource-collection>
<web-resource-name>Application pages</web-resource-name>
<url-pattern>/app/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
<role-name>USER</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>USER</role-name>
</security-role>
<security-role>
<role-name>ADMIN</role-name>
</security-role>
have you the jboss-web.xml? in the src/main/webapp/WEB-INF
jaspitest is the security domain to use HTTP Authentication Mechanism Interface in Wildfly/Jboss
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web version="8.0" xmlns="http://www.jboss.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/schema/jbossas/jboss-web_8_0.xsd">
<context-root/>
<security-domain>jaspitest</security-domain>
</jboss-web>
I just tested the application that you linked and work as Expected
you can configure the session timeout (in minutes) on the web.xml
<session-config>
<session-timeout>
1
</session-timeout>
<cookie-config>
<http-only>true</http-only>
<secure>false</secure>
</cookie-config>
</session-config>
来源:https://stackoverflow.com/questions/64750354/on-a-successful-login-securitycontext-authenticate-returns-authenticationstat