1
votes

I realized a login servlet with spring webflow 2 and spring ldap for user authentication. And so far all is ok.

Now, I’m tryng to introduce spring security in my login flow. So, following the spring web flow reference guide version 2.4.0 (section 8), and the spring security ldap section guide, I adjust my configuration to secure flow. In particular, I’m tryng to secure the success login page for only ROLE_USERS users.

What I tried to make is to extract username, password, and the role of the user from ldap database and use these information to apply security in my flow. So, I makes the following changes to my project:

  1. Add secured attribute to displayLoginSuccessView view state in loginflow.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <flow xmlns="http://www.springframework.org/schema/webflow"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/webflow
    http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd">
    
    <var name="loginCredentials" class="com.folkture.login.LoginCredentials"/>
    
    <view-state id="displayLoginView" view="/WEB-INF/views/display_login.jsp" model="loginCredentials">
    <transition on="loginCredentialsEntered" to="performLoginAction"/>
    </view-state>
    
    <action-state id="performLoginAction">
    
    <evaluate expression="loginService.performLogin(loginCredentials)"/>
    
    <transition to="displayLoginSuccessView"/>
    
    <transition on-exception="com.folkture.login.IncorrectLoginCredentialsException"     to="displayLoginErrorView"/>
    </action-state>
    
    <view-state id="displayLoginSuccessView" view="/WEB-INF/views/display_login_success.jsp">
    <secured attributes="ROLE_USER" />
    </view-state>
    
    <view-state id="displayLoginErrorView" view="/WEB-INF/views/display_login_error.jsp"/>
    
    </flow>
    
  2. Add bean SecurityFlowExecutionListener to webflow-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:beans="http://www.springframework.org/schema/beans"
        xmlns:webflow="http://www.springframework.org/schema/webflow-config"
    xsi:schemaLocation="http://www.springframework.org/schema/webflow-config
    http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.4.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    
    <webflow:flow-executor id="loginFlowExecutor"
        flow-registry="loginFlowRegistry">
        <webflow:flow-execution-listeners>
            <webflow:listener ref="sfel" />
        </webflow:flow-execution-listeners>
    </webflow:flow-executor>
    <webflow:flow-registry id="loginFlowRegistry">
        <!-- Define the flow executor responsible for executing login web flow -->
        <webflow:flow-location id="loginFlow"
            path="/WEB-INF/flows/login-flow.xml" />
    </webflow:flow-registry>
    
    <!-- Installs a listener to apply Spring Security authorities -->
    <bean id="sfel"
        class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
    

  3. Add springSecurityFilterChain to my web.xml

  4. Add spring security configuration in spring-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:ldap="http://www.springframework.org/schema/ldap"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
    http://www.springframework.org/schema/ldap             
    http://www.springframework.org/schema/ldap/spring-ldap.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/security 
    http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    
    <context:property-placeholder location="classpath:/ldap.properties"
        system-properties-mode="OVERRIDE" />
    <context:annotation-config />
    
    <ldap:context-source id="contextSource" url="${sample.ldap.url}"
        base="${sample.ldap.base}" authentication-source-ref="springSecurityAuthenticationSource" />
    
    <ldap:ldap-template id="ldapTemplate"
        context-source-ref="contextSource" />
    
    <bean id="loginService" class="com.folkture.login.LoginService">
        <property name="ldapTemplate" ref="ldapTemplate" />
    </bean>
    
    <bean id="springSecurityAuthenticationSource"
        class="org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource" />
    
    <security:http auto-config="true">
        <security:intercept-url pattern="/**" access="ROLE_USER" />
        <security:form-login login-page="/views/display_login.jsp" />
    </security:http>
    
    <security:authentication-manager>
        <security:ldap-authentication-provider
            user-search-filter="(cn={0})" user-search-base="ou=users"
            group-search-filter="(member={0})" group-search-base="ou=Groups"
            group-role-attribute="cn" />
    </security:authentication-manager> 
    
    <security:ldap-server url="ldap://localhost:389" manager-dn="${sample.ldap.userDn}" 
        manager-password="${sample.ldap.password}" />
    

  5. And this is my java class with login method, LoginService.java

    package com.folkture.login;
    
    import javax.naming.directory.DirContext;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.ldap.NamingException;
    import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback;
    import org.springframework.ldap.core.DirContextOperations;
    import org.springframework.ldap.core.LdapEntryIdentification;
    import org.springframework.ldap.core.LdapTemplate;
    import org.springframework.ldap.filter.AndFilter;
    import org.springframework.ldap.filter.EqualsFilter;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.ldap.authentication.LdapAuthenticator;
    import org.springframework.stereotype.Service;
    
    @Service
    public class LoginService implements LdapAuthenticator{
    @Autowired
    private LdapTemplate ldapTemplate;
    
    public LoginService() {
        super();
    }
    
    public void setLdapTemplate(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
        System.out.println("setLdapTemplate "+ ldapTemplate);
    }
    
    public String performLogin(LoginCredentials loginCredentials) throws Exception{
    
        if(login(loginCredentials.getLoginName(),loginCredentials.getPassword())) 
    
        {
            System.out.println("autenticato!");
            return "success";
        } else {
            throw new IncorrectLoginCredentialsException();
        }
    }
    
    public boolean login(String username, String password) throws Exception{
    
        AndFilter filter = new AndFilter();
        filter.and(new EqualsFilter("objectclass", "inetOrgPerson")).and(new EqualsFilter("cn", username));
    
        if(ldapTemplate.authenticate("ou=users", filter.toString(), password, contextCallback))
            return true;
        return false;
    
    }
    
    AuthenticatedLdapEntryContextCallback contextCallback = new AuthenticatedLdapEntryContextCallback() {
        @SuppressWarnings("deprecation")
        public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) {
            try {
                try {
                    ctx.lookup(ldapEntryIdentification.getRelativeDn());
                } catch (javax.naming.NamingException e) {
                    e.printStackTrace();
                }
            }
            catch (NamingException e) {
                throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeDn(), e);
            }
        }
    };
    
    @Override
    public DirContextOperations authenticate(Authentication authentication) {
        // TODO Auto-generated method stub
        return null;
        }
    }
    

After entering username and password I have the following error.

HTTP Status 500 - Request processing failed; nested exception is org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'performLoginAction' of flow 'loginFlow'
________________________________________
type Exception report   
message Request processing failed; nested exception is org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'performLoginAction' of flow 'loginFlow'
description The server encountered an internal error that prevented it from fulfilling this request.
exception 
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'performLoginAction' of flow 'loginFlow'
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
root cause 
org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'performLoginAction' of flow 'loginFlow'
    org.springframework.webflow.engine.impl.FlowExecutionImpl.wrap(FlowExecutionImpl.java:573)
    org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:263)
    org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:169)
    org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:228)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
root cause 
java.lang.NullPointerException
    org.springframework.security.access.vote.RoleVoter.extractAuthorities(RoleVoter.java:115)
    org.springframework.security.access.vote.RoleVoter.vote(RoleVoter.java:96)
    org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:62)
    org.springframework.webflow.security.SecurityFlowExecutionListener.decide(SecurityFlowExecutionListener.java:109)
    org.springframework.webflow.security.SecurityFlowExecutionListener.stateEntering(SecurityFlowExecutionListener.java:74)
    org.springframework.webflow.engine.impl.FlowExecutionListeners.fireStateEntering(FlowExecutionListeners.java:144)
    org.springframework.webflow.engine.impl.FlowExecutionImpl.setCurrentState(FlowExecutionImpl.java:373)
    org.springframework.webflow.engine.impl.RequestControlContextImpl.setCurrentState(RequestControlContextImpl.java:189)
    org.springframework.webflow.engine.State.enter(State.java:191)
    org.springframework.webflow.engine.Transition.execute(Transition.java:228)
    org.springframework.webflow.engine.impl.FlowExecutionImpl.execute(FlowExecutionImpl.java:395)
    org.springframework.webflow.engine.impl.RequestControlContextImpl.execute(RequestControlContextImpl.java:214)
    org.springframework.webflow.engine.TransitionableState.handleEvent(TransitionableState.java:116)
    org.springframework.webflow.engine.Flow.handleEvent(Flow.java:547)
    org.springframework.webflow.engine.impl.FlowExecutionImpl.handleEvent(FlowExecutionImpl.java:390)
    org.springframework.webflow.engine.impl.RequestControlContextImpl.handleEvent(RequestControlContextImpl.java:210)
    org.springframework.webflow.engine.ActionState.doEnter(ActionState.java:105)
    org.springframework.webflow.engine.State.enter(State.java:194)
    org.springframework.webflow.engine.Transition.execute(Transition.java:228)
    org.springframework.webflow.engine.impl.FlowExecutionImpl.execute(FlowExecutionImpl.java:395)
    org.springframework.webflow.engine.impl.RequestControlContextImpl.execute(RequestControlContextImpl.java:214)
    org.springframework.webflow.engine.TransitionableState.handleEvent(TransitionableState.java:116)
    org.springframework.webflow.engine.Flow.handleEvent(Flow.java:547)
    org.springframework.webflow.engine.impl.FlowExecutionImpl.handleEvent(FlowExecutionImpl.java:390)
    org.springframework.webflow.engine.impl.RequestControlContextImpl.handleEvent(RequestControlContextImpl.java:210)
    org.springframework.webflow.engine.ViewState.handleEvent(ViewState.java:231)
    org.springframework.webflow.engine.ViewState.resume(ViewState.java:195)
    org.springframework.webflow.engine.Flow.resume(Flow.java:537)
    org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:259)
    org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:169)
    org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:228)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)

I think the authentication provider settings in spring-config.xml do not match properly the java class for authentication and authorization. How can I successfully retrieving role from my ldap?

1

1 Answers

1
votes

I solved through ldapAuthenticationManager injection.

I added an authManager alias in spring-config.xml authentication-manager and a property for mapping the java class:

...

<bean id="loginService" class="com.folkture.login.LoginService">
        <property name="ldapTemplate" ref="ldapTemplate" />
        <property name="ldapAuthenticationManager" ref="authManager" />
    </bean>

...

    <s:authentication-manager alias="authManager">
        <s:ldap-authentication-provider
            group-search-filter="(member={0})"
            group-search-base="ou=groups"
            group-role-attribute="cn"
            role-prefix="ROLE_"
            user-search-filter="(cn={0})"
            user-search-base="ou=users"
            server-ref="contextSource" />
    </s:authentication-manager>
...

Then I retrieved the ldapAuthenticationManager thought a AuthenticationManager variable in LoginService.java

...
    @Autowired
    @Qualifier("authManager")
    private AuthenticationManager ldapAuthenticationManager;

public void setLdapAuthenticationManager(AuthenticationManager ldapAuthenticationManager) {
        this.ldapAuthenticationManager = ldapAuthenticationManager;
    }

and changed the login method using ldapAuthenticationManager.authenticate method:

public boolean login(String username, String password){
    Authentication authenticatedUser = null;
    Authentication auth = new UsernamePasswordAuthenticationToken(username, password);
    authenticatedUser = ldapAuthenticationManager.authenticate(auth);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
System.out.println("authenticatedUser role" + authenticatedUser.getAuthorities());
    return true;
    }