我正在使用一个允许用户同时使用 NTLM 登录和表单登录的应用程序。我的意思是用户可以使用他们的 Windows 凭据(使用 jespa)登录,但可以注销并使用检查数据库中的用户/密码的公式。如果您使用 IE8(构建大于 8.0.6001.18702)、IE9、Firefox 或 Chrome,它可以正常工作,但是当您使用 IE7(至少使用 7.0.5730.13)和 IE8(构建 8.0.6001.18702)时。
问题是注销后,当用户尝试使用有效用户重新登录时,会出现以下错误:
java.lang.IllegalStateException: Cannot create a session after the response has been committed
at org.apache.catalina.connector.Request.doGetSession(Request.java:2433)
at org.apache.catalina.connector.Request.getSession(Request.java:2153)
at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:833)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:216)
at org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy.onAuthentication(SessionFixationProtectionStrategy.java:91)
at org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy.onAuthentication(ConcurrentSessionControlStrategy.java:63)
at webapp.security.RedirectConcurrentSessionControlStrategy.onAuthentication(RedirectConcurrentSessionControlStrategy.java:33)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:204)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:381)
at webapp.security.jespa.filter.NtlmSecurityFilter$1.doFilter(NtlmSecurityFilter.java:170)
at jespa.http.HttpSecurityService.doFilter(HttpSecurityService.java:1465)
at webapp.security.jespa.filter.NtlmSecurityFilter.doFilter(NtlmSecurityFilter.java:178)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:381)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:177)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:381)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:79)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:381)
at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:109)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:381)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:168)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:83)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:470)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.valves.RequestDumperValve.invoke(RequestDumperValve.java:156)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:291)
at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:877)
at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:594)
at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1675)
at java.lang.Thread.run(Thread.java:662)
securityContext.xml 如下:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:crypt="http://code.google.com/p/spring-crypto-utils/schema/crypt"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://code.google.com/p/spring-crypto-utils/schema/crypt http://code.google.com/p/spring-crypto-utils/schema/crypt.xsd"
default-lazy-init="true">
<bean id="ntlmEntryPoint" class="webapp.security.jespa.authentication.NtlmAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/beans"/>
<bean id="loginEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint" p:loginFormUrl="/login.html"
xmlns="http://www.springframework.org/schema/beans"/>
<bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"
xmlns="http://www.springframework.org/schema/beans">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationEntryPoint" ref="basicAuthenticationEntryPoint"/>
</bean>
<bean id="basicAuthenticationEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/beans">
<property name="realmName" value="remote eFrame"/>
</bean>
<http access-decision-manager-ref="accessDecisionManager" entry-point-ref="ntlmEntryPoint" security-context-repository-ref="securityContextRepository">
<anonymous/>
<intercept-url pattern="/**" access="PERMISSION_SYSTEM"/>
<custom-filter ref="basicAuthenticationFilter" before="PRE_AUTH_FILTER"/>
<custom-filter ref="concurrentSessionFilter" position="CONCURRENT_SESSION_FILTER"/>
<custom-filter ref="${security.sso}ProcessingFilter" position="PRE_AUTH_FILTER"/>
<!-- TODO make these URLs configurable -->
<form-login login-page="/login.html" default-target-url="/index.html?formlogin=true" always-use-default-target="false"
login-processing-url="/j_spring_security_check" authentication-failure-url="/login.html?login_error=1"
authentication-success-handler-ref="postAuthenticationSuccessHandler"
authentication-failure-handler-ref="postAuthenticationFailureHandler"/>
<session-management invalid-session-url="/sessionExpired.html"
session-authentication-strategy-ref="redirectConcurrentSessionControlStrategy"/>
</http>
<bean id="postAuthenticationSuccessHandler" class="webapp.security.PostAuthenticationSuccessHandler"
xmlns="http://www.springframework.org/schema/beans">
<property name="defaultTargetUrl" value="/index.html?formlogin=true"/>
<property name="alwaysUseDefaultTargetUrl" value="true"/>
</bean>
<bean id="postAuthenticationFailureHandler" class="webapp.security.PostAuthenticationFailureHandler"
p:defaultFailureUrl="/login.html?login_error=1"
xmlns="http://www.springframework.org/schema/beans"/>
<bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"
xmlns="http://www.springframework.org/schema/beans"/>
<bean id="ajaxRedirectStrategy" class="webapp.security.AjaxRedirectStrategy"
xmlns="http://www.springframework.org/schema/beans"/>
<bean id="concurrentSessionFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"
xmlns="http://www.springframework.org/schema/beans">
<property name="sessionRegistry" ref="sessionRegistry"/>
<property name="expiredUrl" value="/sessionExpired.html"/>
<property name="redirectStrategy" ref="ajaxRedirectStrategy"/>
</bean>
<bean id="redirectConcurrentSessionControlStrategy" class="webapp.security.RedirectConcurrentSessionControlStrategy"
xmlns="http://www.springframework.org/schema/beans">
<constructor-arg ref="sessionRegistry"/>
</bean>
<bean id="securityContextRepository" class="webapp.security.SecurityContextRepositoryDecorator" xmlns="http://www.springframework.org/schema/beans">
<property name="target">
<bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository">
</bean>
</property>
<property name="sessionRegistry" ref="sessionRegistry"/>
</bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="ntlmAuthenticationProvider"/>
<authentication-provider ref="databaseAuthenticationProvider"/>
</authentication-manager>
<!-- DATABASE Security Provider -->
<bean id="databaseAuthenticationProvider" class="webapp.security.db.DBAuthenticationProvider"
xmlns="http://www.springframework.org/schema/beans">
<property name="userDetailsService" ref="databaseUserDetailsService"/>
<property name="fallbackPasswordEncoder">
<bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"/>
</property>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>
<bean id="databaseUserDetailsService" class="webapp.security.DBUserDetailsService"
xmlns="http://www.springframework.org/schema/beans"/>
<!-- NTLM provider -->
<bean id="ntlmAuthenticationProvider" class="webapp.security.jespa.provider.NtlmAuthenticationProvider"
xmlns="http://www.springframework.org/schema/beans"/>
<bean id="ntlmProcessingFilter" class="webapp.security.jespa.filter.NtlmSecurityFilter"
p:authenticationEntryPoint-ref="ntlmEntryPoint"
p:authenticationManager-ref="authenticationManager" p:properties-ref="ntlmProperties"
p:authenticationSuccessHandler-ref="postAuthenticationSuccessHandler"
xmlns="http://www.springframework.org/schema/beans"/>
<bean id="ntlmProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"
xmlns="http://www.springframework.org/schema/beans">
<property name="ignoreResourceNotFound" value="true"/>
<property name="localOverride" value="true"/>
<property name="properties">
<props>
<prop key="jespa.bindstr">${jespa.bindstr}</prop>
<prop key="jespa.dns.servers">${jespa.dns.servers}</prop>
<prop key="jespa.dns.site">${jespa.dns.site}</prop>
<prop key="jespa.service.acctname">${jespa.service.acctname}</prop>
<prop key="jespa.service.password">${jespa.service.password}</prop>
<prop key="jespa.log.level">${jespa.log.level}</prop>
<prop key="jespa.account.canonicalForm">${jespa.account.canonicalForm}</prop>
<prop key="fallback.location">${fallback.location}</prop>
<prop key="http.parameter.anonymous.name">formlogin</prop>
<prop key="excludes">/j_spring_security_check,/login.html,/sessionExpired.html,logout.html,/images/*,/css/*,/styles/*,/js/*
</prop>
</props>
</property>
</bean>
<global-method-security jsr250-annotations="enabled" access-decision-manager-ref="accessDecisionManager"/>
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"
xmlns="http://www.springframework.org/schema/beans">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
<property name="rolePrefix" value="PERMISSION_"/>
</bean>
<bean id="jsr250Voter" class="org.springframework.security.access.annotation.Jsr250Voter"/>
<bean id="dataTypeVoter" class="webapp.DataTypeAccessVoter"/>
</list>
</property>
</bean>
最后一个失败的方法是:
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
// nest inside the HttpSecurityService to get access to the authenticated SecurityProvider
FilterChain nestedChain = new FilterChain() {
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse) throws IOException, ServletException {
final HttpServletResponse httpResponse = (HttpServletResponse) response;
if (servletRequest instanceof HttpSecurityServletRequest) {
final HttpSecurityServletRequest securityRequest = (HttpSecurityServletRequest) servletRequest;
SecurityPrincipal principal = (SecurityPrincipal) securityRequest.getUserPrincipal();
setCookie("NTLM-Auth-Capable", "true", (HttpServletRequest) request, httpResponse);
try {
if (authenticationIsRequired(securityRequest, principal)) {
SecurityProvider securityProvider = securityRequest.getSecurityProvider();
NtlmAuthenticationToken authenticationToken = createNtlmAuthenticationToken(principal, securityProvider);
Authentication authResult = authenticationManager.authenticate(authenticationToken);
LOG.debug("Authentication success: {}", authResult.toString());
setCookie("NTLM-Auth-Enabled", "true", (HttpServletRequest) request, httpResponse);
SecurityContextHolder.getContext().setAuthentication(authResult);
onSuccessfulAuthentication((HttpServletRequest) servletRequest, httpResponse, authResult);
}
} catch (AuthenticationException e) {
LOG.debug("Authentication request for NTLM principal [{}] failed: ", principal, e.getMessage());
SecurityContextHolder.getContext().setAuthentication(null);
onUnsuccessfulAuthentication((HttpServletRequest) servletRequest, httpResponse, e);
authenticationEntryPoint.commence(securityRequest, httpResponse, e);
return;
} catch (SecurityProviderException e) {
LOG.debug("Authentication request for NTLM principal [{}] failed: ", principal, e.getMessage());
SecurityContextHolder.getContext().setAuthentication(null);
}
}
// do not use wrapped (Jespa) instance, instead use the originals (Spring wrapped) versions.
chain.doFilter(request, response);
}
};
// call the super doFilter with the nested chain, which should call the code above.
httpSecurityService.doFilter(request, response, nestedChain);
}
在该方法中,当它检查 servletRequest 类时: if (servletRequest instanceof HttpSecurityServletRequest) {
使用失败的 servletRequest 的 IE7 和 IE8是 jespa.http.HttpSecurityServletRequest,但是使用没有失败的 IE8 构建,IE9、FF 和 Chrome的 servlet 请求不是那个。因此,出于某种原因,令人讨厌的 IE 版本似乎继续发送 NTLM 信息,因此 SpringSecurity 尝试进行身份验证。
Spring的版本是3.1.2(核心和安全),jespa是1.1.7,jcifs是1.3.15。
你们中有人遇到过这个问题吗?如果是这样,你做了什么来解决它?
非常感谢!