Página de inicio de sesión JSF personalizada de Spring, siempre "Credenciales incorrectas"

I'm trying to get a JSF login page to work with Spring security. I've looked around for numerous examples but none works. Every time I try to log in using the JSF page I get a "Bad credentials" warning in my server log.

Primavera-Seguridad.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security.xsd">

    <http auto-config="true">
        <intercept-url pattern="/Login.xhtml*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <intercept-url pattern="/**/*.css*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <intercept-url pattern="/**/*.js*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" />
        <form-login login-page="/Login.xhtml" default-target-url="/Secure.xhtml"
            authentication-failure-url="/Login.xhtml" />
    </http>

    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="admin" authorities="ROLE_ADMIN" password="admin"/>
            </user-service>
        </authentication-provider>
    </authentication-manager>
</beans:beans>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.example" />
    <context:annotation-config />
    <tx:annotation-driven />
    <import resource="classpath:spring/security/Spring-Security.xml" />
</beans>

Iniciar sesión.xhtml

<!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://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head></h:head>
<body>
    <h:form>
        <h:outputLabel value="username" for="j_username"
            style="float:left" />
        <h:inputText id="j_username" style="float:left" />

        <h:outputLabel value="password" for="j_password"
            style="float:left; clear:both" />
        <h:inputSecret id="j_password" style="float:left" />

        <h:commandButton value="login"
            actionListener="#{loginBean.login}" style="float:left;clear:both" />
    </h:form>
    <h:messages style="float:left;clear:both" />
</body>
</html>

Iniciar sesión

@Named
@Scope("request")
public class LoginBean
{
    public void login() throws ServletException, IOException
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        externalContext.dispatch("/j_spring_security_check");
        facesContext.responseComplete();
    }
}

Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <filter>
        <filter-name>OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
        <init-param>
            <param-name>singleSession</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>sessionFactoryBeanName</param-name>
            <param-value>sessionFactory</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
</web-app>

When I use a non-JSF page as Login.xhtml it works flawlessly.

Page that does work:

<!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://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head></h:head>
<body>
    <form action="j_spring_security_check" method="post">
        <table>
            <tr>
                <td>User:</td>
                <td><input type="text" name="j_username" /></td>
            </tr>
            <tr>
                <td>Password:</td>
                <td><input type="password" name="j_password" /></td>
            </tr>
            <tr>
                <td colspan='2'><input name="submit" type="submit"
                    value="submit" /></td>
            </tr>
        </table>
    </form>
</body>
</html>

Cualquier ayuda se agradece.

preguntado el 31 de julio de 12 a las 15:07

4 Respuestas

This is an old problem. By default the FilterSecurityInterceptor will only execute once-per-request and doesn't do security re-checking unless there is change in the url but with JSP/JSF forwards the page is rendered as a response to the current request and the url in the browser contains the address of the previous page.

Before Spring Security 3.0 this was bypassed doing a GET request something like this:

String encodedURL = externalcontext.encodeResourceURL(externalcontext.getRequestContextPath() + "/j_spring_security_check?j_username=" + username + "&j_password=" + password);

    externalcontext.redirect(encodedURL);

But from Spring Security 3.0, by default it supports POST only.

So one way, probably the easiest to use is a simple HTML form. Otherwise you need to manually authenticate the request by getting the AuthenticationManager.

I guess the whole story originated from this post on Spring forums.

And the best working example can be found on the ICEFaces wiki

Here is the relevant LoginController class from the tutorial.zip

/**
 * This class handles all login attempts except html forms that directly
 * post to the /j_spring_security_check method.
 *
 * @author Ben Simpson
 */
@ManagedBean(name = "loginController")
@RequestScoped
public class LoginController implements Serializable {
    private static final long serialVersionUID = 1L;


    /**
     * This action logs the user in and returns to the secure area.
     *
     * @return String path to secure area
     */
    public String loginUsingSpringAuthenticationManager() {
        //get backing bean for simple redirect form
        LoginFormBackingBean loginFormBean =
                (LoginFormBackingBean) FacesUtils.getBackingBean("loginFormBean");
        //authentication manager located in  Spring config: /WEB-INF/authenticationContext-security.xml
        AuthenticationManager authenticationManager =
                (AuthenticationManager) getSpringBean("authenticationManager");
        //simple token holder
        Authentication authenticationRequestToken = createAuthenticationToken(loginFormBean);
        //authentication action
        try {
            Authentication authenticationResponseToken =
                authenticationManager.authenticate(authenticationRequestToken);
            SecurityContextHolder.getContext().setAuthentication(authenticationResponseToken);
            //ok, test if authenticated, if yes reroute
            if (authenticationResponseToken.isAuthenticated()) {
                //lookup authentication success url, or find redirect parameter from login bean
                return "/secure/examples";
            }
        } catch (BadCredentialsException badCredentialsException) {
            FacesMessage facesMessage =
                new FacesMessage("Login Failed: please check your username/password and try again.");
            FacesContext.getCurrentInstance().addMessage(null,facesMessage);
        } catch (LockedException lockedException) {
            FacesMessage facesMessage =
                new FacesMessage("Account Locked: please contact your administrator.");
            FacesContext.getCurrentInstance().addMessage(null,facesMessage);
        } catch (DisabledException disabledException) {
            FacesMessage facesMessage =
                new FacesMessage("Account Disabled: please contact your administrator.");
            FacesContext.getCurrentInstance().addMessage(null,facesMessage);
        }

        return null;
    }

    private Authentication createAuthenticationToken(LoginFormBackingBean loginFormBean) {
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(
                        loginFormBean.getUserName(),
                        loginFormBean.getPassword()
                );
        return usernamePasswordAuthenticationToken;
    }


    private Object getSpringBean(String name){
        WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(
                (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext());
        return ctx.getBean(name);
    }
}

OPTION 3 : I haven't personally tried but even this should work:

By setting once-per-request attribute to false in your http element in applicationContext thus forcing security rechecking. But I don't recommend it.

<http auto-config="true" use-expressions="true" once-per-request="false">

Respondido 31 Jul 12, 19:07

Thanks, you're a life saver. With some adjustments and the LoginController code you supplied it worked. The once-per-request="false" solution didn't work on it's own though. - siebz0r

No problem! I am glad it met your requirements. - Ravi Kadaboina

Qué es loginformbacking bean for? - Jeef

The answer to the question left me a little wanting.

So to get this working with a minimal amount of code in the controller (I wanted to avoid manually authenticating), I used a combination of a JSF (primefaces) form and a simple form.

I ended up with a view like this:

<h:form id="login-form" prependId="false">
    <p:focus for="userName" />
    <p:fieldset id="login-fs" legend="User Authentication">
        <h:panelGrid id="login-grid" columns="3">
            <p:outputLabel for="userName" value="User Name" />
            <p:inputText id="userName" value="#{loginView.userName}" required="true" />
            <p:message for="userName" />

            <p:outputLabel for="password" value="Password" />
            <p:inputText type="password" id="password" value="#{loginView.password}" required="true" />
            <p:message for="password" />
        </h:panelGrid>
        <br />
        <p:commandButton value="Submit" icon="ui-icon-check" process="@form" update="login-grid" actionListener="#{loginView.login}" />
    </p:fieldset>
</h:form>

<form id="hidden-form" action="#{request.contextPath}/j_spring_security_check" method="post">
    <h:inputHidden id="j_username" />
    <h:inputHidden id="j_password" />
</form>
<script type="text/javascript">
    function mysubmit() {
        $('#j_username').val($('#userName').val());
        $('#j_password').val($('#password').val());

        $('#hidden-form').submit();
    }
</script>

And the backing bean could do the typical jsf lifecycle, after which it would send javascript back to transfer values from the successfully validated JSF form to the hidden one and submit the hidden form:

@ManagedBean
public class LoginView {
    private String userName;
    private String password;

    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;
    }

    public void login() {
        RequestContext.getCurrentInstance().execute("mysubmit()");
    }
}

You could do anything else you want on the server-side before the submit actually happens, if you need to.

Respondido el 19 de diciembre de 12 a las 03:12

Someone correct me if i'm wrong, but I think you are specifying your backing bean incorrectly.

The correct JSF way to specify your backing bean scope is like this:

@ManagedBean
@RequestScoped
public class LoginBean
{
    public void login() throws ServletException, IOException
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        externalContext.dispatch("/j_spring_security_check");
        facesContext.responseComplete();
    }
}

Respondido 31 Jul 12, 16:07

The OP is using Spring container to manage the beans, instead of JSF's own, that's why he's using different annotations. - Elias Dorneles

Oh ok thanks for the info. Is it possible that Spring's container doesn't play nice with JSF view components? - Bagre

I tried your solution but it didn't make a difference. As @eljunior said, I'm using Spring to manage my beans. - siebz0r

cambiar su h:commandButton to use an action method instead of an actionListener:

<h:commandButton value="login"
        action="#{loginBean.login}" style="float:left;clear:both" />

Ver también: Diferencias entre action y actionListener

contestado el 23 de mayo de 17 a las 12:05

Didn't fix my problem. Nice tip though ;-) - siebz0r

Well... I don't know what else to try, then. Why don't you just leave the plain HTML form, inside the JSF page? - Elias Dorneles

I'm looking for a 'pure' JSF solution. I may have to do other stuff when a user logs in. Pure JSF seems cleaner imho. - siebz0r

I don't know... JSF generates HTML and also allows you to write HTML in the facelets pages. In these times of JSF 2, I don't think there is a need to avoid writing HTML in our pages anymore. :) - Elias Dorneles

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.