Tuesday, April 14, 2015

JSF login through Spring Security (4.0.0)

Add the required entries in web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext-security.xml
        </param-value>
    </context-param>
...
    <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><!-- mandatory to allow the managed bean to forward the request to the filter -->
    </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>

Be sure that springSecurityFilterChain hase the two dispatcher properties set as shown above otherwise the first method will not work

Configure security rules, authentication manager, authentication provider, etc

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:s="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" 
       
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    
        
    <context:annotation-config />
    
    <!--s:http pattern="/facesx/**" security="none"/-->

    <s:http authentication-manager-ref="authenticationManager">

        <s:intercept-url pattern="/faces/auth/admin/**" access="hasRole('ROLE_Admins')" />
        <s:intercept-url pattern="/faces/auth/**" access="authenticated" />
        <s:intercept-url pattern="/faces/login.xhtml" access="permitAll" />

        <!--s:form-login/-->
        <s:form-login login-page="/faces/login.xhtml" default-target-url="/faces/auth/home.xhtml"/>
        <s:csrf disabled="true"/>
    </s:http>


    <s:authentication-manager id="authenticationManager">
        <s:authentication-provider ref='AD-LdapProvider' />
    </s:authentication-manager>

    <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
        <constructor-arg value="ldap://mydomain.local:389"/>
        
        <property name="userDn" value="CN=aduser,OU=App Users,OU=MyCompany,DC=mydomain,DC=local" />
        <property name="password" value="******" />
    </bean>

    <bean id="AD-LdapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
                <constructor-arg ref="contextSource" />
                <property name="userSearch">
                    <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
                        <constructor-arg index="0" value="OU=MyCompany,DC=mydomain,DC=local"/>
                        <constructor-arg index="1" value="(&amp;(objectClass=user)(sAMAccountName={0}))"/>
                        <constructor-arg index="2" ref="contextSource" />
                        <property name="searchSubtree" value="true"/>
                    </bean>
                </property>
            </bean>
        </constructor-arg>
        <constructor-arg>
            <bean class="org.springframework.security.ldap.userdetails.NestedLdapAuthoritiesPopulator">
                <constructor-arg ref="contextSource" />
                <constructor-arg value="OU=MyCompany,DC=mydomain,DC=local" />
                <property name="groupSearchFilter" value="(&amp;(objectClass=group)(groupType:1.2.840.113556.1.4.803:=-2147483648)(member={0}))"/><!--http://ldapwiki.willeke.com/wiki/Active%20Directory%20Group%20Related%20Searches#section-Active+Directory+Group+Related+Searches-AllSecurityGroupsLocalGlobalAndUniversal -->
                <property name="rolePrefix" value="ROLE_"/>
                <property name="searchSubtree" value="true"/>
                <property name="convertToUpperCase" value="false"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>

Remark: groupType:1.2.840.113556.1.4.803:=-2147483648 will filter only security group types

Method 1

Have the login form as follows:

        <h:form prependId="false" id="form">
            <p:panelGrid columns="2" style="border-bottom-width: 0px;">
                <h:outputText value="User:"/>
                <h:inputText value="#{loginController.user}" id="username"/>
                <h:outputText value="Password:"/>
                <h:inputSecret value="#{loginController.password}" id="password"/>
            </p:panelGrid>
            <p:commandButton action="#{loginController.login}" value="Login" ajax="false"/>
        </h:form>

In managed bean, method called at pressing Login button:

Named()
@SessionScoped()
public class LoginController implements Serializable {
...
    public void login() throws ServletException, IOException { 
        
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        RequestDispatcher dispatcher = ((ServletRequest) context.getRequest()).getRequestDispatcher("/login");
        dispatcher.forward((ServletRequest) context.getRequest(), (ServletResponse) context.getResponse());
        FacesContext.getCurrentInstance().responseComplete();
    }
}

Be sure that:

  • you have not overriden the default mapping for login-processing-url (default value "/login" as oposed to "/j_spring_security_check" for older versions of Spring
  • have the ids of user and password input buttons as "username" and "password" respectively (be aware older Spring versions have the defaults set to "j_xxx"; you can actually configure these values from username-parameter and password-parameter attributes of login element)
  • use ajax="false" in button definition
  • add prependId="false" to form definition
  • have the FORWARD and REQUEST dispatcher parameters set as shown above

Method 2

Make all web.xml and applicationContext-security.xml as above, apart from:

<s:form-login login-page="/faces/login.xhtml"/><!-- no  default-target-url -->

Use a regular JSF login page/controller and perform login as usual:

    public String login() {
        try {
            getRequest().login(lastLoginName, password);
            ...
            return "/auth/home?faces-redirect=true";
        } catch (ServletException ex) {
            Logger.getLogger(CommonController.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "/error?faces-redirect=true";

Method 3

Use the same login form as for Method 1 but add onclick method and remove action:

        <h:form prependId="false" id="form">
            <p:panelGrid columns="2" style="border-bottom-width: 0px;">
                <h:outputText value="User:"/>
                <h:inputText value="#{loginController.user}" id="username"/>
                <h:outputText value="Password:"/>
                <h:inputSecret value="#{loginController.password}" id="password"/>
            </p:panelGrid>
            <p:commandButton value="Login" ajax="false" 
 onclick="$('#form').attr('action',$('#form').attr('action').replace('/faces/login.xhtml','/login'))"/>
        </h:form>

The main disatvantage of this method is that you have no chance to do additional processing after successfull login (but you can do it through JSF events in page defined as default-target-url)

5 comments :

  1. Hello!! And thanks!! Save my day.

    Just one question. Do you know why, if we doesn´t disable CSRF Spring makes two calls to the LoginController login() method?

    Thank you again!!!

    ReplyDelete

  2. thank you sooooooo much looking forward to see more tricks :)

    ReplyDelete
  3. Hi,

    Please Look at at this blog ,It explains everything nicely

    https://raichand-java.blogspot.in/2016/12/springsecurity4primefaces5springdatajpa.html

    Thanks
    Raichand

    ReplyDelete
  4. Do you can update that post with JSF 2.2 and Spring Security 4.x?

    ReplyDelete