spring-security整合spring-session

老子叫甜甜 提交于 2020-02-05 09:29:19

引入依赖

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

	&lt;!&ndash; PreAuthentication manager. &ndash;&gt;
	<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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!