简体   繁体   中英

Jakarta EE 10: Why am I being asked to login again after already loggin in?

I am trying to implement role based security to a web app using Jakarta EE 10 and JSF 4.0. I currently have some resources secured with the following url pattern: /myApp/*. (details in web.xml below)

I am using a custom database identity store and a custom form authentication mechanism. I believe this is working correctly since I am being asked to login, and entering appropriate credentials redirects me to the desired page: /myApp/transactions.xhtml.

I have another page, /myApp/import.xhtml. I access this page via a custom component, that currently uses h:commandLink, defined in transactions.xhtml.

My question: why am I being asked to login again when I navigate from transaction.xhtml to import.xhtml? My expectation is that I should be prompted to login once, and then be able to access any secured pages (eg import.xhtml) without having to login again.

I do add my user to the session map (see below Login bean).

Here is the code for the application configuration to do custom database identity store and a custom form authentication mechanism:

@ApplicationScoped
@DatabaseIdentityStoreDefinition(
        dataSourceLookup = "java:/MySqlDS",
        callerQuery = "select enc_password from id_store_callers where username = ?",
        groupsQuery = "select idsg.group_name from id_store_groups idsg, id_store_callers idsc " +
                "where idsg.caller_id = idsc.ids_caller_id and idsc.username = ?"
)
@CustomFormAuthenticationMechanismDefinition(
        loginToContinue = @LoginToContinue(
                loginPage = "/login.xhtml",
                errorPage = "",
                useForwardToLogin = false
        )
)
public class ApplicationConfig {
}

Login page

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets"
      xmlns:f="jakarta.faces.core">
<f:view>
    <h:head>
        <title>Login</title>
    </h:head>
    <h:body>
        <h:form>
            <h:outputLabel for="username" value="Username"/>
            <h:inputText id="username" value="#{cLogin.username}" size="45" />
            <br />
            <h:outputLabel for="password" value="Password"/>
            <h:inputSecret id="password" value="#{cLogin.password}" size="45" />
            <br />
            <h:commandButton value="Login" action="#{cLogin.login()}" ajax="false"/>
        </h:form>
    </h:body>

</f:view>
</html>

Login bean:

@Named("cLogin")
@ViewScoped
public class LoginController implements Serializable {

    @Inject
    SecurityContext securityContext;

    @Inject
    FacesContext facesContext;

    private String username;

    private String password;

    public void login() throws IOException {
        ExternalContext ec = facesContext.getExternalContext();
        HttpServletRequest req = (HttpServletRequest) ec.getRequest();
        HttpServletResponse resp = (HttpServletResponse) ec.getResponse();

        AuthenticationParameters params = AuthenticationParameters.withParams()
                        .credential(
                                new UsernamePasswordCredential(username,password)
                        );
        AuthenticationStatus outcome = securityContext.authenticate(req, resp, params);

        String url = (String) ec.getRequestMap()
                .get(RequestDispatcher.FORWARD_REQUEST_URI);
        System.out.println("url: " + url);
        String query = (String) ec.getRequestMap()
                .get(RequestDispatcher.FORWARD_QUERY_STRING);
        System.out.println("query: " + query);

        if(outcome != AuthenticationStatus.SEND_FAILURE){
            System.out.println("auth status: " + outcome);
            ec.getSessionMap()
                    .putIfAbsent("user",securityContext.getCallerPrincipal().getName());
            System.out.println("session user: " + ec.getSessionMap().get("user"));

            if(url == null && outcome == AuthenticationStatus.SUCCESS){
                url = ec.getRequestContextPath() + "/myApp/transactions.xhtml";
                ec.redirect(url);
            }
            else if(url == null && outcome == AuthenticationStatus.SEND_CONTINUE){
                facesContext.responseComplete();
            }
        }
        else {
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "invalid credentials", null));
        }

    }

    public String logout() throws ServletException {
        ExternalContext ec = facesContext.getExternalContext();
        ((HttpServletRequest)ec.getRequest())
                .logout();
        return "/index.html?faces-redirect=true";
    }

    public String returnHome() {
        return "/myApp/transactions.xhtml?faces-redirect=true";
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <servlet>
        <servlet-name>facesServlet</servlet-name>
        <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>facesServlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
    <security-constraint>
        <display-name>Planner Role</display-name>
        <web-resource-collection>
            <web-resource-name>All Transaction Pages</web-resource-name>
            <url-pattern>/myApp/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>planner</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>JSF resources</web-resource-name>
            <url-pattern>/resources/*</url-pattern>
        </web-resource-collection>
        <auth-constraint />
    </security-constraint>
    <welcome-file-list>
        <welcome-file>/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

transactions.xhtml

<ui:composition
    xmlns:h="jakarta.faces.html"
    xmlns:ui="jakarta.faces.facelets"
    template="/WEB-INF/templates/homeTemplate.xhtml"
    xmlns:bq="jakarta.faces.composite/bqcomp"
>
    <ui:param name="title" value="My Transactions Home" />
    <ui:define name="header">
        <h:link value="Home" outcome="#{cLogin.returnHome()}"/>
        <h:link value="Logout" outcome="#{cLogin.logout()}"/>
    </ui:define>
    <ui:define name="main">
        <h:outputStylesheet library="myApp" name="css/components.css" target="head" />
        <h3>Welcome #{cUser.username}</h3>
        <bq:card title="Import" image="upload.png" navigation="import.xhtml?faces-redirect=true"/>
    </ui:define>
</ui:composition>

card component:

<ui:component xmlns:composite="jakarta.faces.composite"
    xmlns:ui="jakarta.faces.facelets"
    xmlns:h="jakarta.faces.html">
    <composite:interface name="card"
                  displayName="Simple Card component">
        <composite:attribute name="title" required="true" />
        <composite:attribute name="image" required="false" />
        <composite:attribute name="navigation" />
    </composite:interface>
    <composite:implementation>
        <div class="card-outer">
            <div class="card-text">
                <h:outputText value="#{cc.attrs.title}" />
            </div>
            <div class="card-inner">
<h:form>
    <h:commandLink action="#{cUser.cardAction(cc.attrs.navigation)}" >
        <h:graphicImage library="images" name="#{cc.attrs.image}" rendered="#{not empty cc.attrs.image}"/>
    </h:commandLink>
</h:form>


            </div>
        </div>
    </composite:implementation>
</ui:component>

Lastly, I'm a coding enthusiast. Forgive me if there is something basic/fundamental that I'm missing. I'm trying to learn to build my own web app with Java, any feedback is welcome.

Thanks.

I've tried changing the Login bean from ViewScope and SessionScope. Looking at other posts, it seemed like the key is to have a "user" object in the Session Map; but I don't fully understand the significance of the "user" object.

I am using a Database Identity store because I don't want to configure users and roles within the application server; I am defining them in a MySql database. I have a separate been to register new users with the "planner" role. That is working fine.

I want to have secured pages, and I also only expect to login once per session.

Have you tried adding @AutoApplySession to your ApplicationConfig? This should instruct the servlet to store authentication status in Session scope

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM