使用 FORM 身份验证的 Elytron 程序化登录
Elytron programmatic login with FORM authentication
我们目前正在从遗留安全子系统迁移到 Elytron,并在 JBoss EAP 7.3.6 中部署了一个基于 Struts2 的 Web 应用程序,它应该支持多种“风格”的身份验证。
标准的登录方式应该是用户在登录表单 (j_security_check
) 中手动提供凭据并单击相应的按钮。这在我们的设置中与 Elytron 配合得很好。
第二种可能性是,对 Web 应用程序受保护内容的 GET 请求可以包含一个包含 JWT 令牌的自定义 cookie。此 cookie 被 io.undertow.server.HttpHandler
拦截,后者在其 io.undertow.server.HttpHandler#handleRequest
方法中处理传入请求。此处理程序由 io.undertow.servlet.api.DeploymentInfo#addSecurityWrapper
使用 DeploymentInfo
注册,该 DeploymentInfo
由 io.undertow.servlet.ServletExtension
的实现提供。 ServletExtension
在 META-INF/services/io.undertow.servlet.ServletExtension
中注册为服务提供商。
我们 io.undertow.server.HttpHandler#handleRequest
实现中的请求处理从 cookie 中提取 JWT 令牌,pre-validates 它并确定包含的用户名。此用户名和作为密码的令牌用作调用 javax.servlet.http.HttpServletRequest#login
.
的输入
对于旧版安全子系统,服务器的行为是,此登录调用触发了针对配置的旧版安全域的身份验证,并在 Undertow 中创建了一个 session,以便 HTTP 200 响应之前的 GET 请求包含一个 Set-Cookie
header 和一个新的 JSESSIONID
cookie。
对于 Elytron,javax.servlet.http.HttpServletRequest#login
不会执行任何操作,既不会触发针对 Elytron 安全域和安全域的身份验证,也不会触发 session 的创建。浏览器只显示登录表单,该表单应该被描述的拦截过程跳过。
我调试了 JBoss 附带的 javax.servlet.http.HttpServletRequest#login
的实现。我们从调用 login = sc.login(username, password)
的 io.undertow.servlet.spec.HttpServletRequestImpl#login
开始。这个 SecurityContext
,当使用 Elytron 时,是 org.wildfly.elytron.web.undertow.server.SecurityContextImpl
。 org.wildfly.elytron.web.undertow.server.SecurityContextImpl#login
首先检查 if (httpAuthenticator == null)
。 httpAuthenticator
仅在 org.wildfly.elytron.web.undertow.server.SecurityContextImpl#authenticate
中设置,它通过调用 javax.servlet.http.HttpServletRequest#authenticate
.
来调用
这解释了为什么对 io.undertow.servlet.spec.HttpServletRequestImpl#login
的简单调用什么也没做。我尝试先调用 javax.servlet.http.HttpServletRequest#authenticate
,在内部实例化 httpAuthenticator
,然后调用 javax.servlet.http.HttpServletRequest#login
。这至少最终触发了针对配置的 Elytron 安全域和安全域的身份验证和授权。 Authentication/authorization 成功,但 Undertow 仍然没有发出新的 JSESSIONID
cookie,浏览器再次显示登录表单,而不是继续访问受保护的资源。
我目前不知道如何处理这个问题以及如何实现与旧版安全子系统相同的行为。为什么 io.undertow.security.api.SecurityContext
的 Elytron 实现与传统安全性 (io.undertow.security.impl.SecurityContextImpl
) 的实现表现如此不同?我应该如何使用 Elytron 和 javax.servlet.http.HttpServletRequest#login
and/or javax.servlet.http.HttpServletRequest#authenticate
?
以编程方式登录基于 FORM 的 Web 应用程序
所有这些的相关 JBoss 配置如下所示:
逆流:
<application-security-domains>
<application-security-domain name="my_app_security_domain" http-authentication-factory="MyHttpAuthFactory"/>
</application-security-domains>
Elytron:
<security-domains>
<security-domain name="MySecurityDomain" default-realm="MyCachingRealm" permission-mapper="default-permission-mapper">
<realm name="MyCachingRealm" role-decoder="FromRolesAttributeDecoder"/>
</security-domain>
</security-domains>
<security-realms>
<custom-realm name="MyCustomRealm" module="module name redacted" class-name="class name redacted"/>
<caching-realm name="MyCachingRealm" realm="MyCustomRealm" maximum-age="300000"/>
<identity-realm name="local" identity="$local"/>
</security-realms>
<mappers>
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
<permission-mapping>
<principal name="anonymous"/>
<permission-set name="default-permissions"/>
</permission-mapping>
<permission-mapping match-all="true">
<permission-set name="login-permission"/>
<permission-set name="default-permissions"/>
</permission-mapping>
</simple-permission-mapper>
<constant-realm-mapper name="local" realm-name="local"/>
<constant-realm-mapper name="MyRealmMapper" realm-name="MyCachingRealm"/>
<simple-role-decoder name="FromRolesAttributeDecoder" attribute="Roles"/>
</mappers>
<http>
<http-authentication-factory name="MyHttpAuthFactory" security-domain="MySecurityDomain" http-server-mechanism-factory="global">
<mechanism-configuration>
<mechanism mechanism-name="FORM" realm-mapper="MyRealmMapper">
<mechanism-realm realm-name="MyRealm"/>
</mechanism>
</mechanism-configuration>
</http-authentication-factory>
<provider-http-server-mechanism-factory name="global"/>
</http>
这是 JBoss EAP 中的错误,已在 EAP 7.3.8 和 7.4.1 中修复。有关详细信息,请参阅 https://issues.redhat.com/browse/JBEAP-21737 and https://issues.redhat.com/browse/JBEAP-21738。
我们目前正在从遗留安全子系统迁移到 Elytron,并在 JBoss EAP 7.3.6 中部署了一个基于 Struts2 的 Web 应用程序,它应该支持多种“风格”的身份验证。
标准的登录方式应该是用户在登录表单 (j_security_check
) 中手动提供凭据并单击相应的按钮。这在我们的设置中与 Elytron 配合得很好。
第二种可能性是,对 Web 应用程序受保护内容的 GET 请求可以包含一个包含 JWT 令牌的自定义 cookie。此 cookie 被 io.undertow.server.HttpHandler
拦截,后者在其 io.undertow.server.HttpHandler#handleRequest
方法中处理传入请求。此处理程序由 io.undertow.servlet.api.DeploymentInfo#addSecurityWrapper
使用 DeploymentInfo
注册,该 DeploymentInfo
由 io.undertow.servlet.ServletExtension
的实现提供。 ServletExtension
在 META-INF/services/io.undertow.servlet.ServletExtension
中注册为服务提供商。
我们 io.undertow.server.HttpHandler#handleRequest
实现中的请求处理从 cookie 中提取 JWT 令牌,pre-validates 它并确定包含的用户名。此用户名和作为密码的令牌用作调用 javax.servlet.http.HttpServletRequest#login
.
对于旧版安全子系统,服务器的行为是,此登录调用触发了针对配置的旧版安全域的身份验证,并在 Undertow 中创建了一个 session,以便 HTTP 200 响应之前的 GET 请求包含一个 Set-Cookie
header 和一个新的 JSESSIONID
cookie。
对于 Elytron,javax.servlet.http.HttpServletRequest#login
不会执行任何操作,既不会触发针对 Elytron 安全域和安全域的身份验证,也不会触发 session 的创建。浏览器只显示登录表单,该表单应该被描述的拦截过程跳过。
我调试了 JBoss 附带的 javax.servlet.http.HttpServletRequest#login
的实现。我们从调用 login = sc.login(username, password)
的 io.undertow.servlet.spec.HttpServletRequestImpl#login
开始。这个 SecurityContext
,当使用 Elytron 时,是 org.wildfly.elytron.web.undertow.server.SecurityContextImpl
。 org.wildfly.elytron.web.undertow.server.SecurityContextImpl#login
首先检查 if (httpAuthenticator == null)
。 httpAuthenticator
仅在 org.wildfly.elytron.web.undertow.server.SecurityContextImpl#authenticate
中设置,它通过调用 javax.servlet.http.HttpServletRequest#authenticate
.
这解释了为什么对 io.undertow.servlet.spec.HttpServletRequestImpl#login
的简单调用什么也没做。我尝试先调用 javax.servlet.http.HttpServletRequest#authenticate
,在内部实例化 httpAuthenticator
,然后调用 javax.servlet.http.HttpServletRequest#login
。这至少最终触发了针对配置的 Elytron 安全域和安全域的身份验证和授权。 Authentication/authorization 成功,但 Undertow 仍然没有发出新的 JSESSIONID
cookie,浏览器再次显示登录表单,而不是继续访问受保护的资源。
我目前不知道如何处理这个问题以及如何实现与旧版安全子系统相同的行为。为什么 io.undertow.security.api.SecurityContext
的 Elytron 实现与传统安全性 (io.undertow.security.impl.SecurityContextImpl
) 的实现表现如此不同?我应该如何使用 Elytron 和 javax.servlet.http.HttpServletRequest#login
and/or javax.servlet.http.HttpServletRequest#authenticate
?
所有这些的相关 JBoss 配置如下所示:
逆流:
<application-security-domains>
<application-security-domain name="my_app_security_domain" http-authentication-factory="MyHttpAuthFactory"/>
</application-security-domains>
Elytron:
<security-domains>
<security-domain name="MySecurityDomain" default-realm="MyCachingRealm" permission-mapper="default-permission-mapper">
<realm name="MyCachingRealm" role-decoder="FromRolesAttributeDecoder"/>
</security-domain>
</security-domains>
<security-realms>
<custom-realm name="MyCustomRealm" module="module name redacted" class-name="class name redacted"/>
<caching-realm name="MyCachingRealm" realm="MyCustomRealm" maximum-age="300000"/>
<identity-realm name="local" identity="$local"/>
</security-realms>
<mappers>
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
<permission-mapping>
<principal name="anonymous"/>
<permission-set name="default-permissions"/>
</permission-mapping>
<permission-mapping match-all="true">
<permission-set name="login-permission"/>
<permission-set name="default-permissions"/>
</permission-mapping>
</simple-permission-mapper>
<constant-realm-mapper name="local" realm-name="local"/>
<constant-realm-mapper name="MyRealmMapper" realm-name="MyCachingRealm"/>
<simple-role-decoder name="FromRolesAttributeDecoder" attribute="Roles"/>
</mappers>
<http>
<http-authentication-factory name="MyHttpAuthFactory" security-domain="MySecurityDomain" http-server-mechanism-factory="global">
<mechanism-configuration>
<mechanism mechanism-name="FORM" realm-mapper="MyRealmMapper">
<mechanism-realm realm-name="MyRealm"/>
</mechanism>
</mechanism-configuration>
</http-authentication-factory>
<provider-http-server-mechanism-factory name="global"/>
</http>
这是 JBoss EAP 中的错误,已在 EAP 7.3.8 和 7.4.1 中修复。有关详细信息,请参阅 https://issues.redhat.com/browse/JBEAP-21737 and https://issues.redhat.com/browse/JBEAP-21738。