如何通过 ldap 身份验证和授权将安全性应用于登录流程?

Posted

技术标签:

【中文标题】如何通过 ldap 身份验证和授权将安全性应用于登录流程?【英文标题】:How to apply security to login flow through ldap authentication and authorization? 【发布时间】:2015-03-24 19:25:19 【问题描述】:

我用 spring webflow 2 和 spring ldap 实现了一个用于用户身份验证的登录 servlet。到目前为止一切正常。

现在,我尝试在我的登录流程中引入 Spring Security。因此,按照 spring web flow 参考指南 2.4.0 版(第 8 节)和 spring security ldap 部分指南,我调整了我的配置以保护流量。特别是,我试图确保只有 ROLE_USERS 用户的成功登录页面。

我试图从 ldap 数据库中提取用户名、密码和用户角色,并使用这些信息在我的流程中应用安全性。因此,我对我的项目进行了以下更改:

    loginflow.xml

    中为 displayLoginSuccessView 视图状态添加安全​​属性
    <?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>
    

    将 bean SecurityFlowExecutionListener 添加到 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" />
    

    springSecurityFilterChain 添加到我的 web.xml

    spring-config.xml中添加spring安全配置

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

    这是我的带有登录方法的 java 类,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;
        
    
    

输入用户名和密码后出现以下错误。

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)

我认为 spring-config.xml 中的身份验证提供程序设置与用于身份验证和授权的 java 类不匹配。如何从我的 ldap 成功检索角色?

【问题讨论】:

【参考方案1】:

我通过 ldapAuthenticationManager 注入解决了。

我在 spring-config.xml authentication-manager 中添加了一个 authManager 别名和一个用于映射 java 类的属性:

...

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

然后我检索了 ldapAuthenticationManager 以为 LoginService.java

中的 AuthenticationManager 变量
...
    @Autowired
    @Qualifier("authManager")
    private AuthenticationManager ldapAuthenticationManager;

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

并使用 ldapAuthenticationManager.authenticate 方法更改登录方法:

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;
    

【讨论】:

以上是关于如何通过 ldap 身份验证和授权将安全性应用于登录流程?的主要内容,如果未能解决你的问题,请参考以下文章

基于 SAML 的 SSO 用于身份验证和 LDAP 用于授权 - Spring Boot Security

具有数据库授权的 Spring JAAS 身份验证

如何在 Spring 安全性中同时使用数据库和 LDAP 身份验证?

使用 CAS 进行身份验证和 LDAP 进行授权的 Spring 项目

Spring Security - LDAP 身份验证和数据库授权

如何使用带有 LDAP 身份验证的 Apache Shiro 添加角色授权