如何从多个服务器获取与 Spring Security 和 Spring Session 相同的会话

Posted

技术标签:

【中文标题】如何从多个服务器获取与 Spring Security 和 Spring Session 相同的会话【英文标题】:How to get same session with Spring Security and Spring Session From multiple server 【发布时间】:2015-04-11 09:56:59 【问题描述】:

对不起,我的英语还是不太好。 请多多包涵,希望你能理解我的问题..


我有两个网络服务器。 (每个 Web 应用程序都是一样的)

Web 服务器共享一个 redis 服务器。 我使用 Spring Security 和 Spring Session。 当我登录第一台服务器并访问第二台服务器时, 我想自动登录第二个服务器,但不是。

我猜,因为会话 id 与不同的服务器 ip 不同。

如何获取相同的会话 ID?

WEB.XML

<!-- The definition of the Root Spring Container shared by all Servlets 
    and Filters -->
<!-- Loads Spring Security config file -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/root-context.xml,
        /WEB-INF/spring/spring-security.xml,
        /WEB-INF/spring/jedis.xml
            </param-value>
</context-param>

<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Processes application requests -->
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- Encoding -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Session Filter -->
<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Spring Security -->
<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>

jedis.xml

<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value=""<!-- My Server IP --> />
    <property name="port" value="6379" />
    <property name="poolConfig" ref="redisPoolConfig" />
</bean>


<bean id="redisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="testOnBorrow" value="true" />
    <property name="minEvictableIdleTimeMillis" value="60000" />
    <property name="softMinEvictableIdleTimeMillis" value="1800000" />
    <property name="numTestsPerEvictionRun" value="-1" />
    <property name="testOnReturn" value="false" />
    <property name="testWhileIdle" value="true" />
    <property name="timeBetweenEvictionRunsMillis" value="30000" />
</bean>

<!-- string serializer to make redis key more readible  -->
<bean id="stringRedisSerializer"
    class="org.springframework.data.redis.serializer.StringRedisSerializer" />

<!-- redis template definition  -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" >
    <property name="connectionFactory" ref="jedisConnectionFactory" />
    <property name="keySerializer" ref="stringRedisSerializer" />
    <property name="hashKeySerializer" ref="stringRedisSerializer" />
</bean>

spring-security.xml

<http pattern="/resources/**" security="none" />

<http auto-config="true" >
    <session-management session-fixation-protection="changeSessionId">
        <concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/> <!-- I couldn't clear understand of this element-->
    </session-management>
    <intercept-url pattern="/" access="ROLE_ANONYMOUS, ROLE_USER" />
    <intercept-url pattern="/perBoard" access="ROLE_ANONYMOUS, ROLE_USER" />
    <intercept-url pattern="/per" access="ROLE_ANONYMOUS, ROLE_USER" />
    <intercept-url pattern="/perSearchTag" access="ROLE_ANONYMOUS, ROLE_USER" />
    <intercept-url pattern="/**" access="ROLE_USER" />
    <form-login login-page="/" 
                authentication-success-handler-ref="loginSuccessHandler"
                authentication-failure-handler-ref="loginFailureHandler"
                always-use-default-target="true" 
                username-parameter="j_username" 
                password-parameter="j_password"/>
                <!-- default-target-url="/board"  -->
    <logout logout-success-url="/" invalidate-session="true" delete-cookies="true" />
</http>

<authentication-manager>
    <authentication-provider user-service-ref="userDetailsService" />
</authentication-manager>

<beans:bean id="loginSuccessHandler" class=".......LoginSuccessHandler">
    <beans:property name="sqlSession" ref="sqlSession" />
</beans:bean>
<beans:bean id="loginFailureHandler" class=".......LoginFailureHandler" />
<beans:bean id="userDetailsService" class="......UserDetailsServiceImpl">
    <beans:property name="sqlSession" ref="sqlSession" />
</beans:bean>

【问题讨论】:

使用spring security无法实现跨多台服务器的会话管理。它需要一个单独的策略。你可以在这里找到更多细节 - serverfault.com/questions/32421/… 你可以尝试定义session avare负载均衡器。因此,带有会话 ID 的请求被发送到生成会话 ID 的应用实例。 感谢您的评论..但我无法清楚地理解..对不起,您能给我一些代码级别的解决方案吗?? 您需要在 Web 容器之间共享会话。看看***.com/questions/4561950/… 我有同样的问题,并按照上面链接中的说明进行操作。为什么我有不同的 id 会话? 【参考方案1】:

这似乎是一个老问题。但是,看起来可以实现想要的行为。查看http://docs.spring.io/spring-session/docs/current/reference/html5/guides/security.html了解更多详情

创建redis bean

<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="port" value="$app.redis.port" />
    <property name="hostName" value="$app.redis.hostname" />
</bean>

<context:annotation-config />
<bean
    class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>

上面将创建到Redis 服务器的连接,并将创建一个名为springSessionRepositoryFilter 的bean,它将替换常规的HttpSession 实现。

设置弹簧安全

可以通过使用org.springframework.security.web.FilterChainProxy 创建spring filter 即:

<b:bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
    <filter-chain-map request-matcher="ant">
         <filter-chain pattern="/somelocation/" filters="none" />
         <filter-chain pattern="/someotherlocation"
            filters="springSessionRepositoryFilter, somemorespring filters"/>
    </filter-chain-map>
</b:bean>

注意:spring security 的过滤器顺序很重要,不在此答案中。但是为了能够使用spring Session 和redis,第一个过滤器是springSessionRepositoryFilter。更多信息请访问http://docs.spring.io/spring-security/site/docs/3.0.x/reference/security-filter-chain.html

设置 Http 会话

编辑web.xml

<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>

这将允许tomcat 在任何过滤器之前使用springSecurityFilterChain,因此它将允许springSessionRepositoryFilter 成为第一个过滤器。这将导致Spring session 魔法从redis db 中获取session

使用Spring session + spring security 不使用custom spring filters 可以在http://www.jayway.com/2015/05/31/scaling-out-with-spring-session/ 找到

【讨论】:

@durron597 我也有同样的问题。但我不知道发生了什么。我做了教程,但他们没有共享同一个会话。他们正在创造一个新的。

以上是关于如何从多个服务器获取与 Spring Security 和 Spring Session 相同的会话的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Validated Spring RestController 获取多个缺失请求参数的详细信息?

使用 Spring Jpa 从两个或多个表中获取选定的列

如何跨多个 Spring Boot 应用程序共享 H2 内存数据库?

如何让 Spring Boot 客户端的 application.yml 从配置服务器获取值

如何从 Spring WebClient 获取响应 json

Spring Batch中如何读取多个CSV文件合并数据进行处理?