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

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

问题描述

我正在尝试使用 JSF 登录页面来使用 Spring 安全性.我环顾四周寻找了许多示例,但没有一个有效.每次我尝试使用 JSF 页面登录时,我的服务器日志中都会收到错误凭据"警告.

Spring-Security.xml

<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><认证管理器><身份验证提供者><用户服务><user name="admin" authority="ROLE_ADMIN" password="admin"/></用户服务></authentication-provider></authentication-manager></beans:beans>

applicationContext.xml

登录.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><身体><h:形式><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="登录"actionListener="#{loginBean.login}" style="float:left;clear:both"/></h:form><h:messages style="float:left;clear:both"/>

登录豆

@Named@Scope("请求")公共类 LoginBean{public void login() 抛出 ServletException, IOException{FacesContext facesContext = FacesContext.getCurrentInstance();ExternalContext externalContext = facesContext.getExternalContext();externalContext.dispatch("/j_spring_security_check");facesContext.responseComplete();}}

Web.xml

<上下文参数><param-name>javax.faces.PROJECT_STAGE</param-name><param-value>开发</param-value></context-param><过滤器><filter-name>OpenEntityManagerInViewFilter</filter-name><filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class><初始化参数><param-name>singleSession</param-name><param-value>true</param-value></init-param><初始化参数><param-name>sessionFactoryBeanName</param-name><param-value>sessionFactory</param-value></init-param></过滤器><过滤器映射><filter-name>OpenEntityManagerInViewFilter</filter-name><url-pattern>/*</url-pattern></过滤器映射><过滤器><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></过滤器><过滤器映射><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern><调度员>前进</调度员><调度员>请求</调度员></过滤器映射><听众><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></监听器><听众><listener-class>org.springframework.web.context.request.RequestContextListener</listener-class></监听器><小服务程序><servlet-name>Faces Servlet</servlet-name><servlet-class>javax.faces.webapp.FacesServlet</servlet-class><启动时加载>1</启动时加载></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><身体><form action="j_spring_security_check" method="post"><表格><tr><td>用户:</td><td><input type="text" name="j_username"/></td></tr><tr><td>密码:</td><td><input type="password" name="j_password"/></td></tr><tr><td colspan='2'><input name="submit" type="submit"value="提交"/></td></tr></表单>

感谢任何帮助.

解决方案

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

在 Spring Security 3.0 之前,通过这样的 GET 请求绕过了这一点:

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

但是从 Spring Security 3.0 开始,它默认只支持 POST.

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

我猜整个故事起源于这个 在 Spring 论坛上发帖.

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

这是tutorial.zip中的相关LoginController类

/*** 该类处理所有登录尝试,除了直接* 发布到/j_spring_security_check 方法.** @author 本辛普森*/@ManagedBean(name = "loginController")@RequestScoped公共类 LoginController 实现了 Serializable {private static final long serialVersionUID = 1L;/*** 此操作使用户登录并返回安全区域.** @return 安全区域的字符串路径*/public String loginUsingSpringAuthenticationManager() {//获取简单重定向表单的支持beanLoginFormBackingBean loginFormBean =(LoginFormBackingBean) FacesUtils.getBackingBean("loginFormBean");//位于 Spring 配置中的身份验证管理器:/WEB-INF/authenticationContext-security.xmlAuthenticationManager authenticationManager =(AuthenticationManager) getSpringBean("authenticationManager");//简单的代币持有者Authentication authenticationRequestToken = createAuthenticationToken(loginFormBean);//认证动作尝试 {认证 authenticationResponseToken =authenticationManager.authenticate(authenticationRequestToken);SecurityContextHolder.getContext().setAuthentication(authenticationResponseToken);//好的,测试是否通过身份验证,如果是重新路由如果 (authenticationResponseToken.isAuthenticated()) {//查找认证成功url,或者从登录bean中查找重定向参数返回/安全/示例";}} catch (BadCredentialsException badCredentialsException) {FacesMessage facesMessage =new FacesMessage("登录失败:请检查您的用户名/密码,然后重试.");FacesContext.getCurrentInstance().addMessage(null,facesMessage);} catch (LockedExceptionlockedException) {FacesMessage facesMessage =new FacesMessage("账户被锁定:请联系您的管理员.");FacesContext.getCurrentInstance().addMessage(null,facesMessage);} catch (DisabledException disabledException) {FacesMessage facesMessage =new FacesMessage("账号被禁用:请联系您的管理员.");FacesContext.getCurrentInstance().addMessage(null,facesMessage);}返回空;}私有认证 createAuthenticationToken(LoginFormBackingBean loginFormBean) {用户名PasswordAuthenticationToken usernamePasswordAuthenticationToken =新用户名密码AuthenticationToken(loginFormBean.getUserName(),loginFormBean.getPassword());返回 usernamePasswordAuthenticationToken;}私有对象 getSpringBean(字符串名称){WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext((ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext());返回 ctx.getBean(name);}}

选项 3:我还没有亲自尝试过,但即使这样也应该可行:

通过在 applicationContext 的 http 元素中将一次请求属性设置为 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天全站免登陆