为什么 Soteria 的这个简单的 Jakarta Security 示例在 Payara 上有效,但在 WildFly 上无效?

Vít*_*uza 3 java wildfly payara jakarta-ee jakarta-ee-security-api

我习惯于使用 WildFly 进行 Java/Jakarta EE 开发,最近我想将一个使用 JAAS 进行身份验证/授权的项目更新为 Jakarta EE 9.1 中的新 Jakarta Security API。

我无法让它工作,所以我决定创建尽可能简单的示例并在不同的应用程序服务器上进行实验。我从Soteria中获取了一个示例(据我所知,Jakarta Security 的参考实现),并创建了一个新的 Jakarta EE 项目来部署在我的应用程序服务器上。该代码可在此处获取。

reza该示例包含一个非常简单的带有密码的用户身份存储secret1

@ApplicationScoped
public class TestIdentityStore implements IdentityStore {
    public CredentialValidationResult validate(UsernamePasswordCredential usernamePasswordCredential) {

        if (usernamePasswordCredential.compareTo("reza", "secret1")) {
            return new CredentialValidationResult("reza", new HashSet<>(asList("foo", "bar")));
        }

        return INVALID_RESULT;
    }
}
Run Code Online (Sandbox Code Playgroud)

Servlet 包含自定义表单身份验证机制定义,/login.jsf如果用户未通过身份验证,该机制会将用户重定向到该定义。经过身份验证后,它会显示用户名(主体名称)并测试用户角色。还有一个注销功能:

@CustomFormAuthenticationMechanismDefinition(
    loginToContinue = @LoginToContinue(
        loginPage="/login.jsf",
        errorPage="" // DRAFT API - must be set to empty for now
    )
)

@WebServlet("/servlet")
@DeclareRoles({ "foo", "bar", "kaz" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
public class Servlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String webName = null;
        if (request.getUserPrincipal() != null) {
            webName = request.getUserPrincipal().getName();
        }
        
        response.getWriter().write(
                "<html><body> This is a servlet <br><br>\n" +
        
                    "web username: " + webName + "<br><br>\n" +
                            
                    "web user has role \"foo\": " + request.isUserInRole("foo") + "<br>\n" +
                    "web user has role \"bar\": " + request.isUserInRole("bar") + "<br>\n" +
                    "web user has role \"kaz\": " + request.isUserInRole("kaz") + "<br><br>\n" + 

                        
                    "<form method=\"POST\">" +
                        "<input type=\"hidden\" name=\"logout\" value=\"true\"  >" +
                        "<input type=\"submit\" value=\"Logout\">" +
                    "</form>" +
                "</body></html>");
    }
    
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if ("true".equals(request.getParameter("logout"))) {
            request.logout();
            request.getSession().invalidate();
        }
        
        doGet(request, response);
    }
}
Run Code Online (Sandbox Code Playgroud)

login.xhtml如果用户重定向到以下页面,则会呈现该页面/login.jsf

<!DOCTYPE html>
<html lang="en"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:jsf="http://xmlns.jcp.org/jsf">

    <h:head/>
    
    <h:messages />
    
    <body>
        <p>
            Login to continue
        </p>
    
         <form jsf:id="form">
            <p>
                <strong>Username </strong> 
                <input jsf:id="username" type="text" jsf:value="#{loginBacking.username}" />
            </p>
            <p>
                <strong>Password </strong> 
                <input jsf:id="password" type="password" jsf:value="#{loginBacking.password}" />
            </p>
            <p>
                <input type="submit" value="Login" jsf:action="#{loginBacking.login}" />
            </p>
        </form>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

最后,JSF 支持 bean 负责处理表单并调用 Jakarta Securty API 来执行身份验证:

@Named
@RequestScoped
public class LoginBacking {
    @Inject
    private SecurityContext securityContext;

    @NotNull
    @Size(min = 3, max = 15, message="Username must be between 3 and 15 characters")
    private String username;
    
    @NotNull
    @Size(min = 5, max = 50, message="Password must be between 5 and 50 characters")
    private String password;
    
    public void login() {
        FacesContext context = FacesContext.getCurrentInstance();
        Credential credential = new UsernamePasswordCredential(username, new Password(password));
        
        AuthenticationStatus status = securityContext.authenticate(
            getRequest(context),
            getResponse(context), 
            withParams().credential(credential));
        
        if (status.equals(SEND_CONTINUE)) {
            // Authentication mechanism has send a redirect, should not
            // send anything to response from JSF now.
            context.responseComplete();
        } else if (status.equals(SEND_FAILURE)) {
            addError(context, "Authentication failed");
        }
        
    }
    
    private static HttpServletResponse getResponse(FacesContext context) {
        return (HttpServletResponse) context.getExternalContext().getResponse();
    }
    
    private static HttpServletRequest getRequest(FacesContext context) {
        return (HttpServletRequest) context.getExternalContext().getRequest();
    }
    
    private static void addError(FacesContext context, String message) {
        context.addMessage(null, new FacesMessage(SEVERITY_ERROR, message, null));
    }

    /* Getters and setters. */
}
Run Code Online (Sandbox Code Playgroud)

正如我所说,它在 Payara 上按预期工作,但在 WildFly 上我得到以下异常:

ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /jakarta-security-example/login.jsf: jakarta.servlet.ServletException: java.lang.IllegalStateException: java.io.IOException: java.io.IOException: ELY01177: Authorization failed.
    at jakarta.faces.api//jakarta.faces.webapp.FacesServlet.executeLifecyle(FacesServlet.java:699)
    at jakarta.faces.api//jakarta.faces.webapp.FacesServlet.service(FacesServlet.java:437)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
    at io.opentracing.contrib.opentracing-jaxrs2//io.opentracing.contrib.jaxrs2.server.SpanFinishingFilter.doFilter(SpanFinishingFilter.java:52)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at org.wildfly.security.elytron-web.undertow-server@1.10.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.lambda$handleRequest$1(ElytronRunAsHandler.java:68)
    at org.wildfly.security.elytron-base@1.17.1.Final//org.wildfly.security.auth.server.FlexibleIdentityAssociation.runAsFunctionEx(FlexibleIdentityAssociation.java:103)
    at org.wildfly.security.elytron-base@1.17.1.Final//org.wildfly.security.auth.server.Scoped.runAsFunctionEx(Scoped.java:161)
    at org.wildfly.security.elytron-base@1.17.1.Final//org.wildfly.security.auth.server.Scoped.runAs(Scoped.java:73)
    at org.wildfly.security.elytron-web.undertow-server@1.10.0.Final//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.handleRequest(ElytronRunAsHandler.java:67)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.core@2.2.12.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.core@2.2.12.Final//io.undertow.security.handlers.AuthenticationConstraintHandler.handleRequest(AuthenticationConstraintHandler.java:53)
    at io.undertow.core@2.2.12.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.security.ServletSecurityConstraintHandler.handleRequest(ServletSecurityConstraintHandler.java:59)
    at io.undertow.core@2.2.12.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at org.wildfly.security.elytron-web.undertow-server-servlet@1.10.0.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38)
    at io.undertow.core@2.2.12.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow@25.0.1.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    at io.undertow.core@2.2.12.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow@25.0.1.Final//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
    at io.undertow.core@2.2.12.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:280)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at org.wildfly.extension.undertow@25.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
    at org.wildfly.extension.undertow@25.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
    at org.wildfly.extension.undertow@25.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
    at org.wildfly.extension.undertow@25.0.1.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:260)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79)
    at io.undertow.servlet@2.2.12.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100)
    at io.undertow.core@2.2.12.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
    at io.undertow.core@2.2.12.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)
    at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
    at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
    at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
    at org.jboss.xnio@3.8.4.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: jakarta.faces.el.EvaluationException: java.lang.IllegalStateException: java.io.IOException: java.io.IOException: ELY01177: Authorization failed.
    at com.sun.jsf-impl@3.0.0.SP04//com.sun.faces.application.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:77)
    at com.sun.jsf-impl@3.0.0.SP04//com.sun.faces.application.ActionListenerImpl.getNavigationOutcome(ActionListenerImpl.java:75)
    at com.sun.jsf-impl@3.0.0.SP04//com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:64)
    at jakarta.faces.api//jakarta.faces.component.UICommand.broadcast(UICommand.java:213)
    at jakarta.faces.api//jakarta.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:852)
    at jakarta.faces.api//jakarta.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1329)
    at com.sun.jsf-impl@3.0.0.SP04//com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:56)
    at com.sun.jsf-impl@3.0.0.SP04//com.sun.faces.lifecycle.Phase.doPhase(Phase.java:72)
    at com.sun.jsf-impl@3.0.0.SP04//com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:159)
    at jakarta.faces.api//jakarta.faces.webapp.FacesServlet.executeLifecyle(FacesServlet.java:681)
    ... 52 more
Caused by: java.lang.IllegalStateException: java.io.IOException: java.io.IOException: ELY01177: Authorization failed.
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.mechanisms.jaspic.Jaspic.handleCallbacks(Jaspic.java:184)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.mechanisms.jaspic.Jaspic.notifyContainerAboutLogin(Jaspic.java:157)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.mechanisms.HttpMessageContextImpl.notifyContainerAboutLogin(HttpMessageContextImpl.java:261)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.mechanisms.HttpMessageContextImpl.notifyContainerAboutLogin(HttpMessageContextImpl.java:239)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.mechanisms.CustomFormAuthenticationMechanism.validateRequest(CustomFormAuthenticationMechanism.java:53)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.mechanisms.CustomFormAuthenticationMechanism$Proxy$_$$_WeldSubclass.validateRequest$$super(Unknown Source)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.proxy.TerminalAroundInvokeInvocationContext.proceedInternal(TerminalAroundInvokeInvocationContext.java:51)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.proxy.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:78)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.cdi.LoginToContinueInterceptor.processContainerInitiatedAuthentication(LoginToContinueInterceptor.java:182)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.cdi.LoginToContinueInterceptor.validateRequest(LoginToContinueInterceptor.java:98)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.cdi.LoginToContinueInterceptor.intercept(LoginToContinueInterceptor.java:76)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.reader.SimpleInterceptorInvocation$SimpleMethodInvocation.invoke(SimpleInterceptorInvocation.java:73)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.proxy.NonTerminalAroundInvokeInvocationContext.proceedInternal(NonTerminalAroundInvokeInvocationContext.java:66)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.proxy.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:78)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.cdi.AutoApplySessionInterceptor.intercept(AutoApplySessionInterceptor.java:65)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.reader.SimpleInterceptorInvocation$SimpleMethodInvocation.invoke(SimpleInterceptorInvocation.java:73)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeAroundInvoke(InterceptorMethodHandler.java:84)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeInterception(InterceptorMethodHandler.java:72)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.invoke(InterceptorMethodHandler.java:56)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:79)
    at org.jboss.weld.core@4.0.2.Final//org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:68)
    at org.glassfish.soteria@1.0.1-jbossorg-1//org.glassfish.soteria.mechanisms.CustomFormAuthenticationMechanism$Proxy$_$$_WeldSubclass.validateRequest(Unknown Source)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImp

Mar*_*ski 7

解决方案

WildFly 服务器需要额外配置:

  1. 编辑其他应用程序安全域, 在此输入图像描述将Integrated JASPI属性 设置为关闭。 在此输入图像描述

  2. 重新加载服务器 在此输入图像描述

还有一个脚本: https: //github.com/wildfly/quickstart/tree/main/ee-security#configure-the-server

为什么?什么是集成 JASPI?

来自https://docs.wildfly.org/25/WildFly_Elytron_Security.html#Elytron_and_Java_EE_Security

EE 安全 API 构建于 JASPI 之上。在 JASPI 中,我们支持“集成”和“非集成”两种不同的操作模式。 集成模式下,身份验证期间建立的任何身份 都应存在于关联的安全域中。然而,对于 EE 安全 API,很可能会使用替代存储,因此将映射配置为使用“非集成”JASPI 允许根据需要动态创建身份。

  • WildFly 实际上似乎违反了这里的规范,或者至少违反了我在编写规范时的意图;也就是说,安全性应该在没有任何额外的供应商特定配置的情况下发挥作用。 (4认同)
  • 嗯,我发现这个问题很有趣。开箱即用的东西怎么可能不是?我相信您不是第一个也不是最后一个遇到这个问题的人。 (2认同)