HttpServletRequest.login 不会为后续请求保持登录状态

HttpServletRequest.login does not keep logged in for subsequent requests

使用 HttpServletRequest.login(String, String) 登录后,使用下面的代码,根据以下请求,我仍然收到基本身份验证提示。为什么 login 功能在我的配置中不起作用?

我的终点:

@POST
@Path("login")
@Consumes(MediaType.APPLICATION_JSON)
public void login(@Valid LoginRequest loginRequest) {
    try {
        User user = userController.findUserByUsername(loginRequest.getUsername()).orElseThrow(NotFoundException::new);
        httpServletRequest.login(loginRequest.getUsername(), loginRequest.getPassword());
        log.info(securityContext); // not null now!
    }
    catch (ServletException e) {
        throw new NotAuthorizedException(e.getMessage(), e, AuthenticationHeaderFilter.CHALLENGE);
    }
}

还有我的jboss-web.xml

  <?xml version="1.0" encoding="UTF-8"?>
  <jboss-web 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/j2ee/schema/jboss-web_5_1.xsd">
    <security-domain>MyRealm</security-domain>
  </jboss-web>

还有我的 web.xml:

<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>MyRealm</realm-name>
</login-config>

<security-role>
    <role-name>admin</role-name>
</security-role>

<security-role>
    <role-name>user</role-name>
</security-role>

<security-constraint>
    <display-name>Authenticated content</display-name>
    <web-resource-collection>
        <web-resource-name>Authentication required</web-resource-name>
        <url-pattern>/api/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>user</role-name>
    </auth-constraint>
</security-constraint>

<security-constraint>
    <display-name>Anonymous content</display-name>
    <web-resource-collection>
        <web-resource-name>Exclude from Security</web-resource-name>
        <url-pattern>/api/me/login</url-pattern>
    </web-resource-collection>
</security-constraint>

答案是在调用 httpServletRequest#login(String, String) 之后你仍然应该调用 httpSevletRequest#authenticate(HttpServletResponse)。我最终的工作代码是:

httpServletRequest.login(loginRequest.getUsername(), loginRequest.getPassword());
httpServletRequest.authenticate(httpServletResponse);

实际上,HttpServletRequest#login 的合同并未强制要求在 HTTP 会话期间记住经过身份验证的身份(如果已经存在的话),当然也不是应该在成功时创建 HTTP 会话身份验证(如果不存在)。

从技术上讲,HttpServletRequest#login 调用直接进入 identity store(方法的 Javadoc 使用术语 login mechanism)。身份存储是一种数据库,通常只执行凭证验证并且不了解其环境(即不知道 HTTP 会话、远程 EJB 上下文 ID 或 JCA 流入安全 ID 等)。

authentication mechanism 知道它的环境,这个是通过调用 HttpServletRequest#authenticate 来调用的。但是,这通常会在尚未经过身份验证时启动与用户的交互对话,如果用户恰好经过身份验证,则不会记住会话中经过身份验证的身份(这恰好适用于 JBoss 似乎更像是巧合而不是应该发生的事情。

综上所述,Servlet 规范的第 13.10 节 确实 允许容器创建 HTTP 会话:

Containers may create HTTP Session objects to track login state. If a developer creates a session while a user is not authenticated, and the container then authenticates the user, the session visible to developer code after login must be the same session object that was created prior to login occurring so that there is no loss of session information.

(强调我的)

但是...如果这篇文章是关于调用 login() 方法还是 authenticate() 方法,还不是很清楚。

简而言之,这是 Java EE 安全规范中的许多小漏洞之一;它只是没有定义如何以编程方式使用给定的 username/password 进行登录,并明确说明您是否希望仅针对当前请求或 HTTP 会话的其余部分。

我们希望在 Java EE 安全性 API (JSR 375) 中为 Java EE 8 解决此类问题。

由于您需要程序化身份验证,因此 web.xml

中不需要 <login-config>