Tomcat 7 集群 - 会话复制不适用于 spring 引导和 spring 安全
Tomcat 7 clustering - session replication not working with spring boot and spring security
我的 Tomcat 会话复制配置有问题。
在我们公司,我们在 Apache HTTPD 2.4.6 后面使用 Tomcat 7 个 servlet 容器,配置为与 mod_jk/tomcat-connectors 1.2.37 进行负载平衡(运行 在 CentOS 7 x64 上) .会话复制与 Tomcat 管理器一起工作,这意味着如果我们在登录到 HTML 管理器后杀死我们的 Tomcat 之一,我们不需要再次登录(我们有在 web.xml 中将其设置为 <distributable />
。我们可以从 catalina.log:
中看到服务器成功发现彼此
2017.10.06 09:25:15 [INFO] org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded: Replication member added:org.apache.catalina.tribes.membership.StaticMember[tcp://10.35.217.77:4444,10.35.217.77,4444, alive=0, securePort=-1, UDP Port=-1, id={1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 }, payload={}, command={}, domain={100 101 108 116 97 45 115 116 97 ...(12)}, ]
2017.10.06 09:25:15 [INFO] org.apache.catalina.tribes.group.interceptors.TcpFailureDetector performBasicCheck: Suspect member, confirmed alive.[org.apache.catalina.tribes.membership.StaticMember[tcp://10.35.217.77:4444,10.35.217.77,4444, alive=0, securePort=-1, UDP Port=-1, id={1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 }, payload={}, command={}, domain={100 101 108 116 97 45 115 116 97 ...(12)}, ]]
但是,当我们部署示例 Spring 引导应用程序并终止我们对用户进行身份验证的 Tomcat 时,登录提示再次出现。长期以来,我们一直在努力解决这个问题,这让我们抓狂。我们缺少什么?配置如下:
Tomcat/context.xml:
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true" />
<ResourceLink name="jdbc/postgres" global="jdbc/postgres"
type="javax.sql.DataSource" />
</Context>
Tomcat/server.xml:
<Server port="8005" shutdown="SHUTDOWN" address="10.35.217.77">
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
<Resource name="jdbc/postgres" auth="Container" type="javax.sql.DataSource"
username="postgres" password=""
url="jdbc:postgresql://127.0.0.1:5432/postgres"
driverClassName="org.postgresql.Driver"
initialSize="5" maxWait="5000"
maxActive="120" maxIdle="5"
validationQuery="select 1"
poolPreparedStatements="true".
factory="org.apache.commons.dbcp.BasicDataSourceFactory" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector address="0.0.0.0" port="8080" protocol="HTTP/1.1"
connectionTimeout="30000" redirectPort="8443"
enableLookups="false" maxPostSize="20000"
executor="tcThreadPool" />
<Connector address="10.35.217.77" port="8009" protocol="AJP/1.3"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="cluster" jvmRoute="node1">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="6" channelStartOptions="3">
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
autoBind="9" selectorTimeout="5000" maxThreads="6"
address="10.35.217.77" port="4444" />
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" />
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor" />
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector" />
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor" />
<Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor">
<Member className="org.apache.catalina.tribes.membership.StaticMember" securePort="-1"
host="10.35.217.79" port="4444" domain="delta-static"
uniqueId="{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}" />
</Interceptor>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=".*\.gif;.*\.jpg;.*\.png;.*\.css" />
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener" />
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener" />
</Cluster>
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="cluster" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix="log" rotatable="false"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
另一个server.xml类似,交换了.77和.79的IP地址(当然uniqueId也变了)。
SpringBootApp/WebSecurityConfig.java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = Logger.getLogger(WebSecurityConfig.class.getName());
@Bean
public UserDetailsContextMapperImpl contextMapper() {
return new UserDetailsContextMapperImpl();
}
@Autowired
PreferenceDao preferenceDao;
@Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
preferenceDao.findByKey("ldap.domain").getValue(),
preferenceDao.findByKey("ldap.host").getValue()
);
logger.info("Connected to LDAP.");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setSearchFilter("(sAMAccountName={0})");
provider.setUserDetailsContextMapper(contextMapper());
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("s3cr3t")
.roles("USER");
}
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/css/**", "/js/**", "/img/**", "/font/**", "/", "/login?logout").permitAll()
.anyRequest().hasAuthority("ROLE_USER")
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login?logout").permitAll();
}
protected class UserDetailsContextMapperImpl implements UserDetailsContextMapper, Serializable {
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
List<GrantedAuthority> mappedAuthorities = new ArrayList<>();
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username, "", true, true, true, true, mappedAuthorities);
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
//do nothing
}
}
}
如有任何帮助,我们将不胜感激。
看来我终于解决了。集群中的一台机器 运行 是 Tomcat 的先前版本。在我将它从 v7.0.54 升级到 v7.0.69(同时更新 java)之后,一切正常。
我的 Tomcat 会话复制配置有问题。
在我们公司,我们在 Apache HTTPD 2.4.6 后面使用 Tomcat 7 个 servlet 容器,配置为与 mod_jk/tomcat-connectors 1.2.37 进行负载平衡(运行 在 CentOS 7 x64 上) .会话复制与 Tomcat 管理器一起工作,这意味着如果我们在登录到 HTML 管理器后杀死我们的 Tomcat 之一,我们不需要再次登录(我们有在 web.xml 中将其设置为 <distributable />
。我们可以从 catalina.log:
2017.10.06 09:25:15 [INFO] org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded: Replication member added:org.apache.catalina.tribes.membership.StaticMember[tcp://10.35.217.77:4444,10.35.217.77,4444, alive=0, securePort=-1, UDP Port=-1, id={1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 }, payload={}, command={}, domain={100 101 108 116 97 45 115 116 97 ...(12)}, ]
2017.10.06 09:25:15 [INFO] org.apache.catalina.tribes.group.interceptors.TcpFailureDetector performBasicCheck: Suspect member, confirmed alive.[org.apache.catalina.tribes.membership.StaticMember[tcp://10.35.217.77:4444,10.35.217.77,4444, alive=0, securePort=-1, UDP Port=-1, id={1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 }, payload={}, command={}, domain={100 101 108 116 97 45 115 116 97 ...(12)}, ]]
但是,当我们部署示例 Spring 引导应用程序并终止我们对用户进行身份验证的 Tomcat 时,登录提示再次出现。长期以来,我们一直在努力解决这个问题,这让我们抓狂。我们缺少什么?配置如下:
Tomcat/context.xml:
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true" />
<ResourceLink name="jdbc/postgres" global="jdbc/postgres"
type="javax.sql.DataSource" />
</Context>
Tomcat/server.xml:
<Server port="8005" shutdown="SHUTDOWN" address="10.35.217.77">
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
<Resource name="jdbc/postgres" auth="Container" type="javax.sql.DataSource"
username="postgres" password=""
url="jdbc:postgresql://127.0.0.1:5432/postgres"
driverClassName="org.postgresql.Driver"
initialSize="5" maxWait="5000"
maxActive="120" maxIdle="5"
validationQuery="select 1"
poolPreparedStatements="true".
factory="org.apache.commons.dbcp.BasicDataSourceFactory" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector address="0.0.0.0" port="8080" protocol="HTTP/1.1"
connectionTimeout="30000" redirectPort="8443"
enableLookups="false" maxPostSize="20000"
executor="tcThreadPool" />
<Connector address="10.35.217.77" port="8009" protocol="AJP/1.3"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="cluster" jvmRoute="node1">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="6" channelStartOptions="3">
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
autoBind="9" selectorTimeout="5000" maxThreads="6"
address="10.35.217.77" port="4444" />
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" />
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor" />
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector" />
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor" />
<Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor">
<Member className="org.apache.catalina.tribes.membership.StaticMember" securePort="-1"
host="10.35.217.79" port="4444" domain="delta-static"
uniqueId="{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}" />
</Interceptor>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=".*\.gif;.*\.jpg;.*\.png;.*\.css" />
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener" />
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener" />
</Cluster>
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="cluster" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix="log" rotatable="false"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
另一个server.xml类似,交换了.77和.79的IP地址(当然uniqueId也变了)。
SpringBootApp/WebSecurityConfig.java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = Logger.getLogger(WebSecurityConfig.class.getName());
@Bean
public UserDetailsContextMapperImpl contextMapper() {
return new UserDetailsContextMapperImpl();
}
@Autowired
PreferenceDao preferenceDao;
@Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
preferenceDao.findByKey("ldap.domain").getValue(),
preferenceDao.findByKey("ldap.host").getValue()
);
logger.info("Connected to LDAP.");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setSearchFilter("(sAMAccountName={0})");
provider.setUserDetailsContextMapper(contextMapper());
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("s3cr3t")
.roles("USER");
}
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/css/**", "/js/**", "/img/**", "/font/**", "/", "/login?logout").permitAll()
.anyRequest().hasAuthority("ROLE_USER")
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login?logout").permitAll();
}
protected class UserDetailsContextMapperImpl implements UserDetailsContextMapper, Serializable {
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
List<GrantedAuthority> mappedAuthorities = new ArrayList<>();
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username, "", true, true, true, true, mappedAuthorities);
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
//do nothing
}
}
}
如有任何帮助,我们将不胜感激。
看来我终于解决了。集群中的一台机器 运行 是 Tomcat 的先前版本。在我将它从 v7.0.54 升级到 v7.0.69(同时更新 java)之后,一切正常。