2012-07-31 40 views
3

我試圖讓JSF登錄頁面與Spring安全性一起工作。我四處尋找了很多例子,但都沒有成功。每當我嘗試使用JSF頁面登錄時,我的服務器日誌中都會收到「Bad credentials」警告。春季自定義JSF登錄頁面,總是「錯誤的憑據」

彈簧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> 

任何幫助表示讚賞。

回答

3

這是一個老問題。默認情況下,FilterSecurityInterceptor將只執行一次每個請求,並且不會執行安全重新檢查,除非url中有更改,但使用JSP/JSF轉發時,頁面將呈現爲當前請求的響應以及瀏覽器包含前一頁的地址。

的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手動驗證請求。

我想整個故事都源於這個post在Spring論壇上。

和最好的工作示例可以在ICEFaces的wiki

發現這裏是一個教程相關的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); 
    } 
} 

OPTION 3:我沒有親自試過,但即使這應該工作:

通過的applicationContext設置一次,每次請求屬性設置爲false在您的http元素從而迫使安全複查。但我不推薦它。

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

謝謝,你是一個拯救生命的人。通過一些調整和你提供的'LoginController'代碼工作。雖然每次請求一次=「false」解決方案並不適用。 – siebz0r 2012-07-31 18:59:53

+0

沒問題!我很高興它滿足你的要求。 – Ravi 2012-07-31 19:02:11

+0

什麼是loginformbacking bean? – Jeef 2015-05-11 20:54:45

0

有人糾正我,如果我錯了,但我認爲你是錯誤地指定您的支持豆。

指定支撐bean範圍正確的JSF的方法是這樣的:

@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(); 
    } 
} 
+0

OP使用Spring容器來管理bean,而不是JSF自己的,這就是他使用不同註釋的原因。 – elias 2012-07-31 15:08:14

+0

哦,好的,謝謝你的信息。 Spring的容器可能不適合用JSF視圖組件嗎? – Catfish 2012-07-31 15:13:54

+0

我試過你的解決方案,但沒有什麼區別。正如@eljunior所說,我使用Spring來管理我的bean。 – siebz0r 2012-07-31 17:39:17

0

更改h:commandButton,而不是使用一個ActionListener的操作方法:

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

參見:Differences between action and actionListener

+0

沒有解決我的問題。不錯的提示雖然;-) – siebz0r 2012-07-31 17:29:35

+0

那麼......我不知道還有什麼要嘗試,然後。爲什麼不直接在JSF頁面中放置純HTML表單呢? – elias 2012-07-31 17:53:04

+0

我正在尋找一個'純粹'的JSF解決方案。當用戶登錄時,我可能不得不做其他的事情。純JSF似乎更清潔imho。 – siebz0r 2012-07-31 18:14:16

1

問題的答案讓我有點想要。因此,爲了使控制器中的代碼量最小(我想避免手動驗證),我使用了JSF(primefaces)形式和簡單形式的組合。

我結束了這樣的觀點:

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

而且支持bean可以做典型的JSF生命週期後,它會發出的JavaScript回從成功驗證JSF的形式傳遞值隱藏一個並提交隱藏的表單:

@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()"); 
    } 
} 

你可以做任何你想要在服務器端前實際發生的提交,如果你需要。

相關問題