On a successful login, SecurityContext.authenticate() returns AuthenticationStatus.SEND_CONTINUE instead of AuthenticationStatus.SUCCESS

廉价感情. 提交于 2020-12-14 23:45:06

问题


  • 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!