Spring自定义JSF登录页面,始终为“错误的凭证" [英] Spring custom JSF login page, always "Bad credentials"

查看:100
本文介绍了Spring自定义JSF登录页面,始终为“错误的凭证"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图获得一个JSF登录页面以使用Spring安全性.我到处找了很多例子,但都没有用.每次尝试使用JSF页面登录时,我的服务器日志中都会出现错误的凭据"警告.

Spring-Security.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>

Login.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>

LoginBean

@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>

当我将非JSF页面用作Login.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>
    <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>

感谢您的帮助.

解决方案

这是一个老问题.默认情况下,FilterSecurityInterceptor仅对每个请求执行一次,并且不进行安全性重新检查,除非url中有更改,但是使用JSP/JSF转发时,页面将作为对当前请求的响应而呈现,并且url中的URL浏览器包含上一页的地址.

在Spring Security 3.0之前,此操作绕过了GET请求,如下所示:

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

    externalcontext.redirect(encodedURL);

但是从Spring Security 3.0开始,默认情况下它仅支持POST.

因此,一种可能最简单易用的方法是简单的HTML表单.否则,您需要通过获取AuthenticationManager来手动验证请求.

我想整个故事都起源于在春季论坛上发帖.

在ICEFaces Wiki 上可以找到最好的工作示例>

这是tutorial.zip中的相关LoginController类.

/**
 * 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);
    }
}

选项3:我没有亲自尝试过,但即使这样也可以工作:

通过在applicationContext的http元素中将每请求一次"属性设置为false,从而强制进行安全性重新检查.但是我不推荐.

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

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.

Spring-Security.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>

Login.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>

LoginBean

@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>

Any help is appreciated.

解决方案

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">

这篇关于Spring自定义JSF登录页面,始终为“错误的凭证"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆