spring-security整合spring-session
引入依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.3.RELEASE</version>
</dependency>
增加spring-session.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
<!-- tag::beans[] -->
<!--1-->
<context:annotation-config/>
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="7200"/>
</bean>
<!--2-->
<!-- <jdbc:embedded-database id="dataSource" database-name="ZYQJ_MGR" type="H2">
<jdbc:script location="classpath:org/springframework/session/jdbc/schema-oracle.sql" />
</jdbc:embedded-database>-->
<!--3-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>
<!-- end::beans[] -->
</beans>
在原有的spring-security.xml内容基础上,修改配置信息
<bean id="sessionRegistry"
class="org.springframework.session.security.SpringSessionBackedSessionRegistry">
<constructor-arg name="sessionRepository" ref="sessionRepository"/>
</bean>
完整的spring-security.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oauth2="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 配置不过滤的资源(静态资源及登录相关) -->
<!--登录页面和失败页面不进行过滤-->
<security:http security="none" pattern="/login.html" />
<security:http security="none" pattern="/failer.html" />
<security:http security="none" pattern="/api-docs" />
<security:http security="none" pattern="/loginController/loginFail"/>
<!--*********************************************security主配置 开始*****************************************************-->
<!-- entry-point-ref="authenticationEntryPoint" -->
<security:http use-expressions="false"
entry-point-ref="customAuthenticationEntryPoint"
authentication-manager-ref="authenticationManager"
auto-config="false">
<!--访问被拒绝的处理-->
<security:access-denied-handler ref="accessDeniedHandler" />
<!--替换为自定义登陆过滤器-->
<security:custom-filter position="FORM_LOGIN_FILTER" ref="loginFilter" />
<!--所有资源都要进行认证后才能访问-->
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<!-- 自定义登陆页面,
login-page 自定义登陆页面
authentication-failure-url 用户权限校验失败之
后才会跳转到这个页面,如果数据库中没有这个用户则不会跳转到这个页面。
default-target-url 登陆成功后跳转的页面。
注:登陆页面用户名固定 username,密码
password,action:login -->
<!-- <security:form-login login-page="/login.html"
login-processing-url="/login"
username-parameter="username"
password-parameter="password"
authentication-failure-url="/failer.html"
default-target-url="/success.html"
/>-->
<!--登出:invalidate-session
是否删除session logout-url
登出处理链接:logout-success-url 登出成功页面
注:登出操作 只需要链接到 logout即可登出当前用户,在开启跨域CSRF后,注意只能使用POST请求-->
<security:logout invalidate-session="true" logout-url="/logout"
success-handler-ref="customLogoutSuccessHandler" delete-cookies="JSESSIONID" />
<!--*************跨域处理*********-->
<security:headers>
<security:frame-options disabled="true"/>
</security:headers>
<!-- 配置CSRF,处理跨域问题 -->
<security:csrf request-matcher-ref="csrfSecurityRequestMatcher"/>
<!--*************跨域处理结束*********-->
<!--停用对匿名认证的支持 -->
<security:anonymous enabled="false"/>
<!-- 增加权限过滤器,采用数据库方式获取权限 -->
<security:custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
<!--session会话控制:后登陆的将先登录的踢出系统-->
<security:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER" />
<security:session-management
session-authentication-strategy-ref="sas"
invalid-session-url="/loginController/loginFail" />
</security:http>
<!--退出成功的处理-->
<bean id="customLogoutSuccessHandler" class="com.zhiyun.front.common.interceptor.CustomLogoutSuccessHandler"/>
<!--解决跨域请求-->
<bean id="csrfSecurityRequestMatcher" class="com.zhiyun.front.common.interceptor.CsrfSecurityRequestMatcher"/>
<!--访问被拒绝的处理-->
<bean id="accessDeniedHandler" class="com.zhiyun.front.common.interceptor.CustomAccessDeniedHandler"/>
<!--********************************************security主配置 结束******************************************************-->
<!--************************OAuth*********************************-->
<!-- /oauth/token 是oauth2登陆验证请求的url 用于获取access_token ,默认的生存时间是43200秒,即12小时-->
<!--***********************************-->
<!--*******************************用户授权出错时,配置安全框架入口点信息 开始**********************************-->
<!--配置用户未登录时 跳转登陆页面的url-->
<!--当用户没有携带cookie请求服务端时,会跳转到登陆页-->
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<!--配置用户未登录时 跳转登陆页面的url-->
<constructor-arg name="loginFormUrl" value="/login.html" />
</bean>
<!--当用户没有携带cookie或者cookie失效时 请求服务端时,会进入到该类,返回给用户错误信息-->
<bean id="customAuthenticationEntryPoint" class="com.zhiyun.front.common.interceptor.CustomAuthenticationEntryPoint"/>
<!--*******************************用户授权出错时,配置安全框架入口点信息 结束**********************************-->
<!--**************************认证管理器 开始***************************************-->
<security:authentication-manager alias="authenticationManager">
<!--<security:authentication-provider user-service-ref="userService">-->
<!--使用自定义的AuthenticationProvider-->
<security:authentication-provider ref="myAuthenticationProvider">
<!--<security:password-encoder ref="passwordEncoder"/>-->
</security:authentication-provider>
</security:authentication-manager>
<!--**************************认证管理器 结束***************************************-->
<!--**************************登陆过滤器,支持json的方式进行登陆 开始***************************************-->
<bean id="loginFilter" class="com.zhiyun.front.common.interceptor.CustomAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler" ref="authenticationSuccessHandler"/>
<property name="authenticationFailureHandler" ref="authenticationFailureHandler"/>
<!--配置登陆接口的地址-->
<property name="filterProcessesUrl" value="/login"/>
<property name="usernameParameter" value="username"/>
<property name="passwordParameter" value="password"/>
<!--会话管理,需要将会话控制sas加入到登陆过滤器中会话控制才能生效-->
<property name="sessionAuthenticationStrategy" ref="sas" />
</bean>
<!--认证失败的处理-->
<bean id="authenticationFailureHandler" class="com.zhiyun.front.common.interceptor.CustomAuthenticationFailureHandler"/>
<!--认证成功的处理-->
<bean id="authenticationSuccessHandler" class="com.zhiyun.front.common.interceptor.CustomAuthenticationSuccessHandler"/>
<!--********************************登陆过滤器,支持json的方式进行登陆 结束***************************************-->
<!--***********************************授权管理器 开始********************************************-->
<!--自定义权限过滤器,采用数据库方式获取权限并鉴定权限是否合法 -->
<bean id="myFilter"
class="com.zhiyun.front.common.interceptor.AccessSecurityInterceptor">
<!--认证管理器-->
<property name="authenticationManager" ref="authenticationManager" />
<!--鉴权管理器-->
<property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
<!--权限数据源-->
<property name="accessSecurityMetadataSource" ref="securityMetadataSource" />
</bean>
<!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
<!--<bean id="myUserDetailService" class="com.zhiyun.front.system_management.service.impl.CompanyAccountServiceImpl" />-->
<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<bean id="myAccessDecisionManagerBean"
class="com.zhiyun.front.common.interceptor.AccessDecisionManagerImpl"/>
<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
<bean id="securityMetadataSource" class="com.zhiyun.front.common.interceptor.AccessSecurityMetadataSource" >
<!-- 一定要注入SystemRoleDao,虽然该类这里并没有显式的去调用(使用),
但是,在查询权限(SystemPermissionDao)的同时,会去调用SystemRoleDao去查询权限所属的角色信息-->
<property name="permissionDao" ref="systemPermissionDao"/>
<property name="roleDao" ref="systemRoleDao"/>
</bean>
<!--***********************************授权管理器 结束********************************************-->
<!-- **********************sessionManagementFilter:并发会话控制过滤器 开始************************ -->
<bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<!--当session失效时,要跳转的url-->
<constructor-arg name="expiredUrl" value="/loginController/loginFail" />
</bean>
<bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<constructor-arg ref="sessionRegistry"/>
<!--session最大存活数量-->
<property name="maximumSessions" value="5" />
<!--为true同一账户只能登录一次,
为false同一账户可以登录多次如果配置了
org.springframework.security.web.session.ConcurrentSessionFilter
,当session超过最大数量限制时,则会踢出前一个登录的session-->
<property name="exceptionIfMaximumExceeded" value="false" />
</bean>
<bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</bean>
<bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<constructor-arg ref="sessionRegistry"/>
</bean>
</list>
</constructor-arg>
</bean>
<!--security原生的session管理-->
<!--
<bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl">
</bean>
-->
<!--使用spring-session集成后的session管理-->
<bean id="sessionRegistry"
class="org.springframework.session.security.SpringSessionBackedSessionRegistry">
<constructor-arg name="sessionRepository" ref="sessionRepository"/>
</bean>
<!-- **********************sessionManagementFilter:并发会话控制过滤器 结束************************ -->
<!--
<bean id="preTokenAuthenticationFilter"
class="com.will.security.token.PreRequestHeaderAuthenticationFilter">
<property name="authenticationManager" ref="preAuthenticationManager" />
<property name="authenticationFailureHandler" ref="authFailureHandler"/>
<property name="principalRequestHeader" value="X-Auth-Token"/>
<property name="continueFilterChainOnUnsuccessfulAuthentication" value="false" />
</bean>
<!– PreAuthentication manager. –>
<bean id="authFailureHandler" class="org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler" >
<constructor-arg value="/service/secure/admin/login/failed" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="tokenUserDetailsService"/>
</bean>
</property>
<property name="userDetailsChecker">
<bean id="tokenUserDetailsChecker" class="com.will.security.token.TokenUserDetailsChecker" />
</property>
</bean>-->
</beans>
web.xml配置文件中增加如下配置,使spring-session生效
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:applicationContext.xml,
classpath*:spring-session.xml,
classpath*:spring-security.xml
</param-value>
</context-param>
<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>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
修改web.xml中spring-security过滤器配置
之所以要修改该配置,是因为在整合过程中,虽然spring-session已经生效,但是在访问spring-security登陆接口时,会出现session不一致的情况,spring-security过滤器中使用的是原始的session,然而在登录完成后,后续接口调用中,使用的是spring-session包装过后的session,这就导致了,登陆接口与其他业务接口session不一致的问题,因此,我在这里重写了DelegatingFilterProxy类: SecurityDelegatingFilterProxy
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<!--spring-security默认过滤器-->
<!--
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
-->
<!--自定义重写后的过滤器,解决,使用spring-session后,登陆接口和其他接口session不一致的问题-->
<filter-class>com.zhiyun.front.common.interceptor.SecurityDelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
SecurityDelegatingFilterProxy.java代码
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SecurityDelegatingFilterProxy extends DelegatingFilterProxy {
/*
* (non-Javadoc)
*
* @see
* org.springframework.web.filter.DelegatingFilterProxy#invokeDelegate(javax
* .servlet.Filter, javax.servlet.ServletRequest,
* javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
protected void invokeDelegate(Filter delegate, ServletRequest request,
ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request instanceof HttpServletRequest
&& response instanceof HttpServletResponse) {
HttpServletRequest contextHolderRequest = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
//request对象是原始的session对象,contextHolderRequest是spring-session封装后的session
//此处进行对比,如果不相同,则将原始的替换成spring-session封装后的
if (request != contextHolderRequest) {
RequestContextHolder.setRequestAttributes(
new ServletRequestAttributes(
(HttpServletRequest) request,
(HttpServletResponse) response), true);
}
}
super.invokeDelegate(delegate, request, response, filterChain);
}
}
在没有到达springmvc的DispatcherServlet前,RequestContextHolder中都是原始的HttpServletRequest,而自定义的security的filter是在DispatcherServlet之前执行的,所以filter获得的request是原始的。web.xml中security的filter自己来实现,在super.invokeDelegate之前先判断RequestContextHolder的request是不是包装后的session,不是的话自己set进去.
附上完整的web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 配置加载类路径的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:applicationContext.xml,
classpath*:spring-session.xml,
classpath*:spring-security.xml
</param-value>
</context-param>
<!-- 配置监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置监听器,监听request域对象的创建和销毁的 -->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<!--激活Tomcat的defaultServlet来处理静态文件
要写在DispatcherServlet的前面, 让 defaultServlet先拦截请求,这样请求就不会进入Spring了
解决访问静态页面,springmvc拦截后报404的问题-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!-- 前端控制器(加载classpath:springmvc.xml 服务器启动创建servlet) -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 服务器启动的时候,让DispatcherServlet对象创建 -->
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--spring-session-->
<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>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<!-- 解决中文乱码过滤器 -->
<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>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 委派过滤器spring security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<!--spring-security默认过滤器-->
<!--
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
-->
<!--自定义重写后的过滤器,解决,使用spring-session后,登陆接口和其他接口session不一致的问题-->
<filter-class>com.zhiyun.front.common.interceptor.SecurityDelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--这个监听器会在session创建和销毁的时候通知Spring Security。-->
<!--在会话结束时通知SessionRegistryImpl-->
<!--如果不添加该监听器,一旦用户超过了他们的会话限额,
就再也不能登录了,即使用户退出或者会话超时也不行-->
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<!-- <welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>-->
</web-app>
至此spring-security与spring*session整合完毕
感谢大神的解答:
[1]: https://www.oschina.net/question/1245392_2276925
来源:CSDN
作者:weixin_43789433
链接:https://blog.csdn.net/weixin_43789433/article/details/103927030