복붙노트

[SPRING] 봄 사용자 정의 JSF 로그인 페이지, 항상 "잘못된 자격 증명"

SPRING

봄 사용자 정의 JSF 로그인 페이지, 항상 "잘못된 자격 증명"

봄 보안과 함께 작동하도록 JSF 로그인 페이지를 얻으려고합니다. 수많은 예제를 둘러 보았지만 아무 것도 작동하지 않습니다. JSF 페이지를 사용하여 로그인 할 때마다 서버 로그에 "Bad credentials"경고가 표시됩니다.

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

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

Login.xhtml과 같은 비 JSF 페이지를 사용할 때 완벽하게 작동합니다.

작동하는 페이지 :

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

어떤 도움을 주셔서 감사합니다.

해결법

  1. ==============================

    1.이것은 오래된 문제입니다. 기본적으로 FilterSecurityInterceptor는 요청에 한 번만 실행하고 URL에 변경 사항이 없으면 보안 재검사를하지 않지만 JSP / JSF 전달을 통해 페이지가 현재 요청에 대한 응답으로 렌더링되고 URL은 브라우저에는 이전 페이지의 주소가 포함됩니다.

    이것은 오래된 문제입니다. 기본적으로 FilterSecurityInterceptor는 요청에 한 번만 실행하고 URL에 변경 사항이 없으면 보안 재검사를하지 않지만 JSP / JSF 전달을 통해 페이지가 현재 요청에 대한 응답으로 렌더링되고 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 위키에서 찾을 수 있습니다.

    다음은 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 요소에서 once-per-request 속성을 false로 설정하여 보안 재확인을 강제합니다. 그러나 나는 그것을 추천하지 않는다.

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

    2.질문에 대한 대답은 나에게 약간의 욕구를 남겼다.

    질문에 대한 대답은 나에게 약간의 욕구를 남겼다.

    따라서 컨트롤러에서 최소한의 코드로이 작업을 수행하려면 수동으로 인증하는 것을 피하고 싶었습니다. JSF (primeface) 형식과 간단한 형식을 조합하여 사용했습니다.

    나는 이와 같은 견해로 끝났다.

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

    그리고 backing bean은 일반적인 jsf 라이프 사이클을 수행 할 수 있습니다. 그 후에 자바 스크립트를 보내서 성공적으로 검증 된 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()");
        }
    }
    

    필요한 경우 실제로 제출하기 전에 서버 측에서 원하는 것을 수행 할 수 있습니다.

  3. ==============================

    3.누군가 내가 틀렸다면 정정 해 주겠지 만, 당신이 틀린 콩을 잘못 지정했다고 생각합니다.

    누군가 내가 틀렸다면 정정 해 주겠지 만, 당신이 틀린 콩을 잘못 지정했다고 생각합니다.

    백킹 빈 범위를 지정하는 올바른 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();
        }
    }
    
  4. ==============================

    4.h : commandButton을 변경하여 actionListener 대신 action 메소드를 사용하십시오.

    h : commandButton을 변경하여 actionListener 대신 action 메소드를 사용하십시오.

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

    Action과 actionListener의 차이점

  5. from https://stackoverflow.com/questions/11742283/spring-custom-jsf-login-page-always-bad-credentials by cc-by-sa and MIT license