成功登录后,SecurityContext.authenticate() 返回 AuthenticationStatus.SEND_CONTINUE 而不是 AuthenticationStatus.SUCCESS

问题描述 投票:0回答:2
  • 雅加达 EE 8
  • 野蝇21
  • Java 11

使用 Java EE Security,我正在一个简单的应用程序中尝试自定义表单身份验证。

这些是相关文件(问题的描述位于文件下方):

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;
        }
    }
}

登录.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>

登录.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))
        );
    }   
}

问题是,成功登录后(用户名和密码正确),正如您在上面 Login 类中的注释中看到的那样,方法

executeUserAuthentication()
返回
AuthenticationStatus.SEND_CONTINUE
而不是
AuthenticationStatus.SUCCESS
。下面是在执行时以调试模式运行的应用程序的图像,以显示这一点:

结果,浏览器地址栏没有更新为真实的url(start.xhtml),因为上面代码中的

AuthenticationStatus.SUCCESS
分支没有被执行:

我以为我做错了什么,但后来我决定从 @ArjanTijms 克隆这个使用相同身份验证逻辑的简单应用程序:

https://github.com/rieckpil/blog-tutorials/tree/master/jsf-simple-login-with-java-ee-security-api

他的申请在这篇文章中有解释:

https://rieckpil.de/howto-simple-form-based-authentication-for-jsf-2-3-with-java-ee-8-security-api/

结果与我的应用程序相同:执行了

AuthenticationStatus.SEND_CONTINUE
分支而不是
AuthenticationStatus.SUCCESS
分支:

地址栏也没有更新:

那么,这是怎么回事?这是应用程序中的问题吗,Wildfly,这是正确的行为吗(如果是这样,

AuthenticationStatus.SUCCESS
枚举有什么用,什么时候会使用它?)?我只是想能够自己执行重定向。

更新1

供您参考,在

executeUserAuthentication()
之后,用户已通过身份验证:
((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getUserPrincipal()
不为空。

应用程序中的一些其他文件:

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";
        }
    }
}

更新2

经过大量测试,我注意到(至少在我的应用程序中)有时

SecurityContext.authenticate()
返回
AuthenticationStatus.SEND_CONTINUE
,有时返回
AuthenticationStatus.SUCCESS
。到现在我还没弄清楚原因。

所以,我暂时决定通过 hack 来解决

AuthenticationStatus.SEND_CONTINUE
被返回的情况。所以,基本上,我所做的就是手动重定向到 start 页面。黑客如下:

web.xml(将欢迎页面更改为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");
    }
}

这并不优雅,但暂时解决了我的问题。希望有一天你们中的一些人知道我的应用程序到底会发生什么,并可以给我一些提示。

java jsf jakarta-ee wildfly
2个回答
0
投票

AuthenticationStatus 主要由 HttpAuthenticationMechanism 用作返回值,以指示身份验证过程的结果(状态)。

SEND_CONTINUE :调用了身份验证机制,并启动了与调用者的多步骤身份验证对话(例如,调用者已重定向到登录页面)。简单地说身份验证“正在进行中”。收到此状态时,调用应用程序代码(如果有)不应写入响应。 Java文档

你的 web.xml 是什么样的?

您添加了安全限制吗?

示例:

  <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>

你有jboss-web.xml吗?在 src/main/webapp/WEB-INF 中

jaspitest是Wildfly/Jboss中使用HTTP认证机制接口的安全域

<?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>

我刚刚测试了您链接的应用程序并按预期工作

您可以在 web.xml 上配置会话超时(以分钟为单位)

<session-config>
    <session-timeout>
        1
    </session-timeout>
    <cookie-config>
        <http-only>true</http-only>
        <secure>false</secure>
    </cookie-config>
</session-config>

0
投票

我在 Payara 6 上的 Jakarta EE 10 应用程序中也有完全相同的情况,但仍然不明白何时以及为什么会收到 AuthenticationStatus.SEND_CONTINUE...我只注意到注销后的一件事

ExternalContext ec = fc.getExternalContext(); ((HttpServletRequest) ec.getRequest()).logout();

新的登录请求带有 AuthenticationStatus.SUCCESS

© www.soinside.com 2019 - 2024. All rights reserved.