我有一个带有 spring security 3.1 和 Ldap 集成的应用程序。以下是到目前为止的需求和实现中的关键点:
- 应用程序将为单个用户提供多个角色,但这些角色在 ldap 中不存在,因此应用程序仅对来自 ldap 的用户名(或用户 ID)进行身份验证。
- 角色分别存储在数据库中
- 从 ldap 成功认证后,通过实现 UserDetailsService 将 userdetails 和角色设置为主体对象自定义 userdetails 对象
问题:
- 用户 A 登录应用程序
- 用户 B 登录应用程序,用户 A 会话被破坏(这不应该发生,因为用户 A 尚未注销!)
- 用户 B 注销 用户 A 未找到页面,因为用户 B 登录时其会话已被破坏。
applicationContext-security.xml 如下所示:
<beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login.jsp" />
<beans:property name="forceHttps" value="true" />
</beans:bean>
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/login.jsp?login_error=2" />
</beans:bean>
<beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<beans:constructor-arg value="/login.jsp" />
<beans:constructor-arg>
<beans:list>
<beans:ref bean="logoutEventBroadcaster" />
<beans:bean id="securityContextLogoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
</beans:list>
</beans:constructor-arg>
<beans:property name="filterProcessesUrl" value="/j_spring_security_logout" />
</beans:bean>
<beans:bean id="myAuthFilter" class="com.*.security.CustomAuthenticationProcessingFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationFailureHandler" ref="failureHandler" />
<beans:property name="authenticationSuccessHandler" ref="successHandler" />
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="adAuthenticationProvider" />
</authentication-manager>
<beans:bean id="adAuthenticationProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="*.*.net" />
<beans:constructor-arg value="ldap://*.*.net:389/" />
<beans:property name="userDetailsContextMapper">
<beans:bean class="com.ezadvice.service.CustomUserDetailsContextMapper" />
</beans:property>
<beans:property name="useAuthenticationRequestCredentials" value="true" />
</beans:bean>
<beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login.jsp?login_error=1" />
</beans:bean>
<beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/home.do" />
<beans:property name="alwaysUseDefaultTargetUrl" value="true"/>
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
<beans:property name="migrateSessionAttributes" value="false" />
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
CustomAuthenticationProcessingFilter 类如下所示:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String roleId = request.getParameter("roleId");
String username = request.getParameter("j_username");
TbEzaLoginHistory tbEzaLoginHistory = null;
// check if the user has authority for the role
TbEzaUser tbEzaUser = userManagementService.checkUserAndRole(roleId, username);
if (null != tbEzaUser) {
tbEzaLoginHistory = userManagementService.saveLoginHistory(tbEzaUser, roleId);
request.setAttribute("loginHistoryId", tbEzaLoginHistory.getLoginKey());
request.setAttribute("roleId", roleId);
request.setAttribute("j_username", username);
if (UserTracker.increment(username, roleId)) {
try{
Authentication attemptAuthentication = super.attemptAuthentication(request, response);
if (null != attemptAuthentication) {
CustomUser principal = (CustomUser) attemptAuthentication.getPrincipal();
if (null == principal && null != tbEzaLoginHistory)
userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey());
return attemptAuthentication;
}
}
catch (CommunicationException e) {
userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey());
UserTracker.decrement(username, roleId);
RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=5");
try {
dispatcher.forward(request, response);
} catch (ServletException se) {
se.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
LOGGER.debug("Connection Timeout error for UserName:"+username +"\n" + e);
e.printStackTrace();
}
}else {
if (null != tbEzaLoginHistory)
userManagementService.deleteFromLoginHistory(tbEzaLoginHistory.getLoginKey());
RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=4");
try {
dispatcher.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
RequestDispatcher dispatcher = request.getRequestDispatcher("/login.jsp?login_error=3");
try {
dispatcher.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(EXITLOGGER + " attemptAuthentication");
}
return null;
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, authResult);
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authResult;
WebAuthenticationDetails details = (WebAuthenticationDetails) token.getDetails();
String address = details.getRemoteAddress();
CustomUser user = (CustomUser) authResult.getPrincipal();
String userName = user.getUsername();
System.out.println("Successful login from remote address: " + address + " by username: "+ userName);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(ENTRYLOGGER + " unsuccessfulAuthentication");
}
try {
Long loginHistoryId = (Long) request.getAttribute("loginHistoryId");
String username = (String) request.getAttribute("j_username");
String roleId = (String) request.getAttribute("roleId");
userManagementService.deleteFromLoginHistory(loginHistoryId);
super.unsuccessfulAuthentication(request, response, failed);
UserTracker.decrement(username, roleId);
} catch (Exception e) {
e.printStackTrace();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(EXITLOGGER + " unsuccessfulAuthentication");
}
}
UserTracker 类如下所示:
public class UserTracker {
private static Set<String> loggedInUsersDetails = new HashSet<String>();
@SuppressWarnings("unchecked")
synchronized public static boolean increment(String userName, String roleId) {
if(loggedInUsersDetails.add(userName.toLowerCase()+'~'+roleId)){
return true;
}else
return false;
}
synchronized public static void decrement(String userName, String roleId) {
loggedInUsersDetails.remove(userName.toLowerCase()+'~'+roleId);
}
谁能帮我找出为什么用户 A 的会话被破坏了?