Apache Ignite Spring 安全错误
Apache Ignite Spring secutiry error
我正在尝试开发具有 spring 安全登录功能的应用程序,同时使用 apache Ignite 分发用户会话。
- 服务器:阿帕奇Tomcat 8
- Spring版本:4.2.2.RELEASE
- 点燃版本:2.1.0
我的申请有两个错误。
- 从应用程序注销时记录异常。除此之外,会话失效按预期完成。
12-Aug-2017 14:09:01.580 SEVERE [http-nio-8080-exec-2] org.apache.ignite.logger.java.JavaLogger.error Failed to update web session: null
java.lang.NullPointerException
at org.apache.ignite.cache.websession.WebSessionFilter$RequestWrapperV2.getSession(WebSessionFilter.java:1001)
at org.apache.ignite.cache.websession.WebSessionFilter.doFilterV2(WebSessionFilter.java:564)
at org.apache.ignite.cache.websession.WebSessionFilter.doFilterDispatch(WebSessionFilter.java:407)
at org.apache.ignite.cache.websession.WebSessionFilter.doFilter(WebSessionFilter.java:383)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
- 当两台tomcat服务器在两个不同的端口启动相同应用程序部署时,只能从一台服务器访问登录页面。
(如果我从第一个服务器访问登录页面,页面加载正常。但是如果我再次尝试从第二个服务器访问登录页面,它会出错。但是一旦登录应用程序按预期工作并且可以访问分布式会话来自两个服务器。
- 首次访问
- 来自另一台服务器的第二次访问
我的配置文件如下
Spring上下文配置
<beans xmlns.... >
<context:component-scan base-package="test.ignite.spring"/>
<mvc:annotation-driven/>
<context:property-placeholder location="classpath:system.properties" ignore-resource-not-found="true" ignore-unresolvable="true"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<mvc:interceptors>
<mvc:interceptor>
<!-- Cache of HTML pages -->
<mvc:mapping path="/**"/>
<bean class="org.springframework.web.servlet.mvc.WebContentInterceptor">
<property name="cacheSeconds" value="0"/>
</bean>
</mvc:interceptor>
</mvc:interceptors>
</beans>
登录控制器
@Controller
public class LoginController {
@RequestMapping(value = {"login", "/"})
public String login() {
try {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
return "home";
}
return "login";
} catch (Exception e) {
return "redirect:/error/500";
}
}
@RequestMapping(value = "/home")
public String home() {
return "home";
}
}
----更新----
Ignite 配置(整个文件内容)
<beans xmlns.... >
<bean abstract="true" id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
<!-- Set to true to enable distributed class loading for examples, default is false. -->
<property name="peerClassLoadingEnabled" value="true"/>
<property name="cacheConfiguration">
<list>
<bean class="org.apache.ignite.configuration.CacheConfiguration">
<property name="name" value="example"/>
<property name="cacheMode" value="PARTITIONED"/>
</bean>
</list>
</property>
<!-- Enable task execution events for examples. -->
<property name="includeEventTypes">
<list>
<!--Task execution events-->
<util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_STARTED"/>
<util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_FINISHED"/>
<util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_FAILED"/>
<util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_TIMEDOUT"/>
<util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_SESSION_ATTR_SET"/>
<util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_REDUCED"/>
<!--Cache events-->
<util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_PUT"/>
<util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_READ"/>
<util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_REMOVED"/>
</list>
</property>
<!-- Explicitly configure TCP discovery SPI to provide list of initial nodes. -->
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<property name="ipFinder">
<!--
Ignite provides several options for automatic discovery that can be used
instead os static IP based discovery. For information on all options refer
to our documentation: http://apacheignite.readme.io/docs/cluster-config
-->
<!-- Uncomment static IP finder to enable static-based discovery of initial nodes. -->
<!--<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">-->
<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">
<property name="addresses">
<list>
<!-- In distributed environment, replace with actual host IP address. -->
<value>127.0.0.1:47500..47509</value>
</list>
</property>
</bean>
</property>
</bean>
</property>
</bean>
Spring 安全配置
<beans:beans xmlns.... >
<http auto-config="true" create-session="always" use-expressions="true" >
<form-login login-page="/login" default-target-url="/home" authentication-failure-url="/login?error" username-parameter="username" password-parameter="password" always-use-default-target="true"/>
<logout invalidate-session="true" logout-success-url="/login" delete-cookies="JSESSIONID"/>
<session-management session-fixation-protection="newSession" invalid-session-url="/" session-authentication-error-url="/login">
<concurrency-control session-registry-alias="sessionRegistry" max-sessions="10" expired-url="/" error-if-maximum-exceeded="true"/>
</session-management>
<access-denied-handler error-page="/403"/>
</http>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService">
</authentication-provider>
</authentication-manager>
</beans:beans>
web.xml
<web-app xmlns... >
<listener>
<listener-class>org.apache.ignite.startup.servlet.ServletContextListenerStartup</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<filter>
<filter-name>IgniteWebSessionsFilter</filter-name>
<filter-class>org.apache.ignite.cache.websession.WebSessionFilter</filter-class>
</filter>
<!-- You can also specify a custom URL pattern. -->
<filter-mapping>
<filter-name>IgniteWebSessionsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Specify Ignite configuration (relative to META-INF folder or Ignite_HOME). -->
<context-param>
<param-name>IgniteConfigurationFilePath</param-name>
<param-value>example-ignite.xml</param-value>
</context-param>
<!-- Specify the name of Ignite cache for web sessions. -->
<context-param>
<param-name>IgniteWebSessionsCacheName</param-name>
<param-value>example</param-value>
</context-param>
<!--SERVLETS-->
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc-dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:mvc-dispatcher-servlet.xml,
classpath:security-config.xml
</param-value>
</context-param>
</web-app>
如果您能提供任何解决方案/想法来解决问题,我将不胜感激。
由于您的两个部署都在本地主机上,因此它们将共享 JSESSIONID
cookie。到目前为止一切顺利。
然而,Tomcat、Spring Security 和 Ignite 之间似乎存在不匹配,这导致 Spring Security 将来自 Ignite 的匿名会话视为无效(即,未登录)。我还不明白这与您应该已经登录的情况有何关系。
您或许可以使用一种解决方法:从您的 Spring 安全配置中删除 invalid-session-url="/"
。这将防止重定向循环行为。这也会导致用户在 cookie 过期时静默注销,而不是被引导至 /login
。
我已经删除了我之前的回答,因为它遗漏了重点。
我正在尝试开发具有 spring 安全登录功能的应用程序,同时使用 apache Ignite 分发用户会话。
- 服务器:阿帕奇Tomcat 8
- Spring版本:4.2.2.RELEASE
- 点燃版本:2.1.0
我的申请有两个错误。
- 从应用程序注销时记录异常。除此之外,会话失效按预期完成。
12-Aug-2017 14:09:01.580 SEVERE [http-nio-8080-exec-2] org.apache.ignite.logger.java.JavaLogger.error Failed to update web session: null java.lang.NullPointerException at org.apache.ignite.cache.websession.WebSessionFilter$RequestWrapperV2.getSession(WebSessionFilter.java:1001) at org.apache.ignite.cache.websession.WebSessionFilter.doFilterV2(WebSessionFilter.java:564) at org.apache.ignite.cache.websession.WebSessionFilter.doFilterDispatch(WebSessionFilter.java:407) at org.apache.ignite.cache.websession.WebSessionFilter.doFilter(WebSessionFilter.java:383) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
- 当两台tomcat服务器在两个不同的端口启动相同应用程序部署时,只能从一台服务器访问登录页面。
(如果我从第一个服务器访问登录页面,页面加载正常。但是如果我再次尝试从第二个服务器访问登录页面,它会出错。但是一旦登录应用程序按预期工作并且可以访问分布式会话来自两个服务器。
- 首次访问
- 来自另一台服务器的第二次访问
我的配置文件如下
Spring上下文配置
<beans xmlns.... > <context:component-scan base-package="test.ignite.spring"/> <mvc:annotation-driven/> <context:property-placeholder location="classpath:system.properties" ignore-resource-not-found="true" ignore-unresolvable="true"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:interceptors> <mvc:interceptor> <!-- Cache of HTML pages --> <mvc:mapping path="/**"/> <bean class="org.springframework.web.servlet.mvc.WebContentInterceptor"> <property name="cacheSeconds" value="0"/> </bean> </mvc:interceptor> </mvc:interceptors> </beans>
登录控制器
@Controller public class LoginController { @RequestMapping(value = {"login", "/"}) public String login() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (!(auth instanceof AnonymousAuthenticationToken)) { return "home"; } return "login"; } catch (Exception e) { return "redirect:/error/500"; } } @RequestMapping(value = "/home") public String home() { return "home"; } }
----更新----
Ignite 配置(整个文件内容)
<beans xmlns.... > <bean abstract="true" id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"> <!-- Set to true to enable distributed class loading for examples, default is false. --> <property name="peerClassLoadingEnabled" value="true"/> <property name="cacheConfiguration"> <list> <bean class="org.apache.ignite.configuration.CacheConfiguration"> <property name="name" value="example"/> <property name="cacheMode" value="PARTITIONED"/> </bean> </list> </property> <!-- Enable task execution events for examples. --> <property name="includeEventTypes"> <list> <!--Task execution events--> <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_STARTED"/> <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_FINISHED"/> <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_FAILED"/> <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_TIMEDOUT"/> <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_SESSION_ATTR_SET"/> <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_REDUCED"/> <!--Cache events--> <util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_PUT"/> <util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_READ"/> <util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_REMOVED"/> </list> </property> <!-- Explicitly configure TCP discovery SPI to provide list of initial nodes. --> <property name="discoverySpi"> <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi"> <property name="ipFinder"> <!-- Ignite provides several options for automatic discovery that can be used instead os static IP based discovery. For information on all options refer to our documentation: http://apacheignite.readme.io/docs/cluster-config --> <!-- Uncomment static IP finder to enable static-based discovery of initial nodes. --> <!--<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">--> <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder"> <property name="addresses"> <list> <!-- In distributed environment, replace with actual host IP address. --> <value>127.0.0.1:47500..47509</value> </list> </property> </bean> </property> </bean> </property> </bean>
Spring 安全配置
<beans:beans xmlns.... > <http auto-config="true" create-session="always" use-expressions="true" > <form-login login-page="/login" default-target-url="/home" authentication-failure-url="/login?error" username-parameter="username" password-parameter="password" always-use-default-target="true"/> <logout invalidate-session="true" logout-success-url="/login" delete-cookies="JSESSIONID"/> <session-management session-fixation-protection="newSession" invalid-session-url="/" session-authentication-error-url="/login"> <concurrency-control session-registry-alias="sessionRegistry" max-sessions="10" expired-url="/" error-if-maximum-exceeded="true"/> </session-management> <access-denied-handler error-page="/403"/> </http> <authentication-manager> <authentication-provider user-service-ref="userDetailsService"> </authentication-provider> </authentication-manager> </beans:beans>
web.xml
<web-app xmlns... > <listener> <listener-class>org.apache.ignite.startup.servlet.ServletContextListenerStartup</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> <filter> <filter-name>IgniteWebSessionsFilter</filter-name> <filter-class>org.apache.ignite.cache.websession.WebSessionFilter</filter-class> </filter> <!-- You can also specify a custom URL pattern. --> <filter-mapping> <filter-name>IgniteWebSessionsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Specify Ignite configuration (relative to META-INF folder or Ignite_HOME). --> <context-param> <param-name>IgniteConfigurationFilePath</param-name> <param-value>example-ignite.xml</param-value> </context-param> <!-- Specify the name of Ignite cache for web sessions. --> <context-param> <param-name>IgniteWebSessionsCacheName</param-name> <param-value>example</param-value> </context-param> <!--SERVLETS--> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:mvc-dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:mvc-dispatcher-servlet.xml, classpath:security-config.xml </param-value> </context-param> </web-app>
如果您能提供任何解决方案/想法来解决问题,我将不胜感激。
由于您的两个部署都在本地主机上,因此它们将共享 JSESSIONID
cookie。到目前为止一切顺利。
然而,Tomcat、Spring Security 和 Ignite 之间似乎存在不匹配,这导致 Spring Security 将来自 Ignite 的匿名会话视为无效(即,未登录)。我还不明白这与您应该已经登录的情况有何关系。
您或许可以使用一种解决方法:从您的 Spring 安全配置中删除 invalid-session-url="/"
。这将防止重定向循环行为。这也会导致用户在 cookie 过期时静默注销,而不是被引导至 /login
。
我已经删除了我之前的回答,因为它遗漏了重点。