Reputation: 1
I'm attempting to implement a simple form based authentication based with Jakarta Security 3.0.
A (very) minimal (but working) code shows the process: loginout.xhtml login calls LoginOutBean login() method. The login() method in turn calls Security#authenticate which calls the CustomAuthentication class (implementing HttpAuthenticationMechanism) validateRequest() method. The CustomAuthentication in the real world would make a database or LDAP call to validate the login request, returning success or failure.
But I am unable to get the failure portion to work. Failure will not return to the caller method for me to display an error message. Validation success, however, does return and allows me to display a message. Examples of this that I have seen use return httpMsgContext.responseUnauthorized()
but it does not function for me, it simply generates a 401 page. I can trap for 401 in web.xml, but this does not allow me to display a message on the loginout page. As shown in the code below, I have tried several methods to get a return to the SEND_FAILURE case (which displays a failure message) but with no success (as outlined in the code comments).
I suspect I am missing something but I can find no working example to handle validation failure. I would be grateful for any pointers.
loginout.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 class:
@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;
}
}
CustomAuthentication class:
@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"));
}
}
Upvotes: 0
Views: 85