我正在尝试基于 Jakarta 10.0 Faces 和 Glassfish 7 实现基于简单表单的身份验证。
一个(非常)最小(但有效)的代码显示了该过程:loginout.xhtml 登录调用 LoginOutBean login() 方法。 login() 方法依次调用 Security#authenticate,后者调用 CustomAuthentication 类(实现 HttpAuthenticationMechanism)的 validateRequest() 方法。 现实世界中的 CustomAuthentication 将进行数据库或 LDAP 调用来验证登录请求,返回成功或失败。
但是我无法让失败部分工作。失败不会返回到调用者方法让我显示错误消息。 然而,验证成功确实返回并允许我显示一条消息。 我见过使用 return
httpMsgContext.responseUnauthorized()
的例子,但它对我不起作用,它只是生成一个 401 页面。 我可以在 web.xml 中捕获 401,但这不允许我在登录页面上显示消息。 如下面的代码所示,我尝试了多种方法来返回 SEND_FAILURE 情况(显示失败消息),但没有成功(如代码注释中所述)。
我怀疑我遗漏了一些东西,但我找不到任何有效的示例来处理验证失败。 如果有任何指点,我将不胜感激。
登录.xhtml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:em="jakarta.faces.composite/emcomp">
<h:head>
<title>Login and logout</title>
</h:head>
<h:body>
<div>Login/logout</div>
<div>
<h:form id="loginoutform">
<h:commandButton id="loginSubmit" type="submit" value="Login" action="#{loginOutBean.login()}"/>
<br />
<h:commandButton id="logoutSubmit" type="submit" value="Logout" action="#{loginOutBean.logout}"/>
<br />
<h:messages id="globalMsgs" />
</h:form>
</div>
</h:body>
</html>
LoginOutBean 类:
@Named(value = "loginOutBean")
@SessionScoped
public class LoginOutBean implements Serializable {
private static final long serialVersionUID = 290524L;
private static final Logger logger = Logger.getLogger("LoginOutBean");
@Inject private SecurityContext securityContext;
@Inject private FacesContext facesContext;
@Inject private ExternalContext externalContext;
public String login() throws IOException {
switch( continueAuthentication() ) {
case SEND_CONTINUE -> {
logger.log(Level.INFO,"SEND_CONTINUE");
facesContext.responseComplete();
}
case SEND_FAILURE -> {
logger.log(Level.SEVERE,"SEND_FAILURE");
// If gets here, following fails with FacesException of
// "org.jboss.weld.bean.proxy.ProxyMethodHandler.getInstance()"
// is null
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Login failed", null));
}
case SUCCESS -> {
logger.info("SUCCESS");
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_INFO, "Login success!", null));
}
case NOT_DONE -> {}
}
// Shouldn't get here
return null;
}
public String logout() throws ServletException {
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
request.logout();
request.getSession().invalidate();
return "/loginout?faces-redirect=true";
}
private AuthenticationStatus continueAuthentication() {
AuthenticationStatus as = securityContext.authenticate(
(HttpServletRequest) externalContext.getRequest(),
(HttpServletResponse) externalContext.getResponse(),
AuthenticationParameters.withParams()
.credential(new UsernamePasswordCredential("fred", "mysecret"))
);
logger.info("Authentication status: " +as.toString());
return as;
}
}
自定义身份验证类:
@ApplicationScoped
public class CustomAuthentication implements HttpAuthenticationMechanism {
private static final Logger logger = Logger.getLogger("CustomAuthentication");
@Override
public AuthenticationStatus validateRequest( HttpServletRequest request,
HttpServletResponse response,
HttpMessageContext httpMsgContext ) throws AuthenticationException {
logger.info("****************>>>> validateRequest()...");
/*
* "Authentication" fails...
*/
// On launch, doesn't display login screen but validateRequest() is called
// which doesn't return to caller, just generates HTTP 404 - Not Found page
// return httpMsgContext.responseNotFound();
// On launch, doesn't display login screen but validateRequest() is called
// which doesn't return to caller, just generates HTTP 401 - Unauthorized
// page. This is the line seen mostly in examples (eg: Baeldung)
return httpMsgContext.responseUnauthorized();
// On launch, doesn't display login screen but validateRequest() is called
// which doesn't return to caller, just generates blank screen
// return httpMsgContext.notifyContainerAboutLogin(CredentialValidationResult.NOT_VALIDATED_RESULT);
// On launch, doesn't display login screen but validateRequest() is called
// which doesn't return to caller, just generates blank screen
// return httpMsgContext.notifyContainerAboutLogin(CredentialValidationResult.INVALID_RESULT);
// On launch, displays login page and validateRequest() is called.
// Clicking login button then fires authentication *twice* and a whole
// raft of IllegalStateExceptions are thrown followed by authentication
// status of SEND_FAILURE or NOT_DONE, returning to caller (which
// subsequently fails with FacesException).
// return httpMsgContext.forward("/loginout.xhtml");
// On launch, doesn't display login page but validateRequest() is called,
// return is not made to caller, no exceptions are thrown and a blank
// page is displayed.
// return AuthenticationStatus.SEND_FAILURE;
// On launch, doesn't display login page but validateRequest() is called,
// return is not made to caller, no exceptions are thrown and a blank
// page is displayed.
// return AuthenticationStatus.SEND_CONTINUE;
// On launch, doesn't display login page but validateRequest() is called
// which doesn't return to caller, instead throws "Jakarta Authentication:
// Exception during validateRequest" and shows HTTP 500 page
// return null;
/*
* "Authentication" success...
*/
// Following works...
// On launch, displays login page, clicking login button returns Success
// to caller which displays success message.
// return httpMsgContext.doNothing();
// Following works...
// On launch, displays login page, clicking login button returns Success
// to caller which displays success message.
// return AuthenticationStatus.SUCCESS;
// Following works...
// On launch, displays login page, clicking login button returns Success
// to caller which displays success message.
// return httpMsgContext.notifyContainerAboutLogin("fred", Set.of("user"));
}
}