如何通过 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 安全性中同时使用数据库和 LDAP 身份验证?
使用 CAS 进行身份验证和 LDAP 进行授权的 Spring 项目