【Spring Security实战系列】Spring Security实战(七)

版权声明:【本文为博主原创文章或收集整理,未经博主允许不得转载】 https://blog.csdn.net/zsq520520/article/details/77881977
在之前的实战四中一笔带过式的讲了下RememberMe记住密码的功能,那篇的Remember功能是最简易的配置,其功能和安全性都不强。这里就配置下security中RememberMe的各种方式。本人也是在学习中,若有错误,欢迎指正,一起学习。
一 概述
RememberMe 是指用户在网站上能够在 Session 之间记住登录用户的身份的凭证,通俗的来说就是用户登陆成功认证一次之后在制定的一定时间内可以不用再输入用户名和密码进行自动登录。这个过程中通过服务端发送一个 cookie 给客户端浏览器保存,下次浏览器再访问服务端时服务端能够自动检测客户端的 cookie,根据 cookie 值触发自动登录操作。Spring Security中的 Remember-Me 功能通常有两种实现方式。一种是简单的使用加密来保证基于 cookie 的 token 的安全,另一种是通过数据库或其它持久化存储机制来保存生成的 token。
两种方式的区别:第一中方式不安全,就是说在用户获取到实现记住我功能的 token 后,任何用户都可以在该 token 过期之前通过该 token 进行自动登录。如果用户发现自己的 token 被盗用了,那么他可以通过改变自己的登录密码来立即使其所有的记住我 token 失效。如果希望我们的应用能够更安全,那就使用第二种方式。第二种方式也是详细要讲解的。
二 基于简单加密的方式
需要特别注意的是,这两种方式在配置的时候都要提供一个UserDetailsService,这个东西其实就是之前配置的jdbc-user-service标签的一个实现类,配置代码如下:
<beans:bean id="userDetailsService"
                class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
        <beans:property name="usersByUsernameQuery"
                        value="select username,password,status as enabled from user where username = ?" />
        <beans:property name="authoritiesByUsernameQuery"
                        value="select user.username,role.name from user,role,user_role
                                where user.id=user_role.user_id and
                                user_role.role_id=role.id and user.username=?" />
        <beans:property name="dataSource" ref="mysqlDataSource" />
    </beans:bean>
说明:dataSource就是连接数据库的数据源;usersByUsernameQuery就是配置jdbc-user-service时候的users-by-username-query,这个是根据用户名来查询用户的sql语句;同理authoritiesByUsernameQuery就是对应的authorities-by-username-query,这个用来根据用户名查询对应的权限。
下面来配置rememberMe的过滤器:
<beans:bean id="rememberMeFilter"
                class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
        <beans:property name="rememberMeServices" ref="rememberMeServices" />
        <beans:property name="authenticationManager" ref="authenticationManager" />
    </beans:bean>
这个过滤器仅仅这样配置是不会起作用的,还要把它加入的Security的FilterChain中去,用<custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>即可,另外这个过滤器要提供一个rememberMeServices和一个用户认证的authenticationManager,后面这个其实就是authentication-manager所配置的东西,而前面这个需要另外配置,配置方式如下:
<beans:bean id="rememberMeServices"
                class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
        <beans:property name="userDetailsService" ref="userDetailsService" />
        <beans:property name="key" value="zmc" />
        <!-- 指定 request 中包含的用户是否选择了记住我的参数名 -->
        <beans:property name="parameter" value="rememberMe"/>
    </beans:bean>
这个配置中的userDetailsService就是上面配置的,直接引用上面的即可;key就是token中的key,这个key可以用来方式token被修改,另外这个key要和后面配置的rememberMeAuthenticationProvider的key要一样;parameter就是登陆界面的点击记住密码的checkbox的name值,这个一定要一直,要不然没有效果的。
初次之外还要配置一个用户记住密码做认证的authenticationManager
<beans:bean id="rememberMeAuthenticationProvider"
                class="org.springframework.security.authentication.RememberMeAuthenticationProvider" >
        <beans:property name="key" value="zmc" />
    </beans:bean>
同时还要将其添加到authentication-manager标签中去
<authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="userDetailsService">
        </authentication-provider>
        <!-- 记住密码 -->
        <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider>
    </authentication-manager>
最后面将rememberMeServices添加到myUsernamePasswordAuthenticationFilter中去。
最终的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:sec="http://www.springframework.org/schema/security"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security.xsd">


    <!-- 配置不过滤的资源(静态资源及登录相关).是忽略拦截某些资源的意思,主要是针对静态资源 -->
    <http pattern="/**/*.css" security="none"></http>
    <http pattern="/**/*.jpg" security="none"></http>
    <http pattern="/**/*.jpeg" security="none"></http>
    <http pattern="/**/*.gif" security="none"></http>
    <http pattern="/**/*.png" security="none"></http>
    <http pattern="/js/*.js" security="none"></http>

    <http pattern="/login.jsp" security="none"></http>
    <http pattern="/getCode" security="none" /><!-- 不过滤验证码 -->
    <http pattern="/test/**" security="none"></http><!-- 不过滤测试内容 -->

    <http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">

        <!--<intercept-url pattern="/login.jsp" access="permitAll" />-->
        <!-- 表示访问app.jsp时,需要ROLE_SERVICE权限 -->
        <!--<intercept-url pattern="/adminPage.jsp" access="hasRole('ROLE_ADMIN')"></intercept-url>-->
        <!--表示访问任何资源都需要ROLE_ADMIN权限。-->
        <!-- <intercept-url pattern="/**" access="hasRole('ROLE_USER')"></intercept-url>-->

        <!--
            登陆页面肯定是不能拦截的,任何人都应该可以访问,
            <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />配置表示允许匿名用户访问,
            就是不用身份都可以访问;
            还有另一种配置方式:<http pattern="/login.jsp" security="none"></http>,这种配置达到的目的都是一样的。
        -->

        <!--
            form-login这个标签是配置登陆页面的,其中的属性login-page是配置登陆页面的,
            default-target-url配置登陆成功后跳转到的页面,
            authentication-failure-url配置认证失败后的跳转页面。

            form-login标签中还有一个特别要注意的属性use-expressions,如果设置为true,
            这配置access就要做相应的改变,否则项目启动的时候会报错。
            如果use-expressns="true"时,则表示改为 SpEL 表达式。 SpEL 允许使用特定的访问控制规则表达式语言。
            与简单的字符串如 ROLE_USER 不同,配置文件可以指明表达式语言触发方法调用、引用系统属性、计算机值等等。
            如 :<intercept-url pattern="/login.jsp" access="permitAll" />
        -->
        <!--<form-login login-page="/login.jsp" default-target-url="/index.jsp"
                    authentication-failure-url="/login.jsp?error=true"></form-login>-->

        <!--
            logout这个标签用来配置退出或者注销,其中的属性invalidate-session,
            配置否是要清除session,logout-success-url配置注销成功后的跳转页面,
            logout-url提交退出或者注销的地址,因此我们在配置退出或者注销的时候,
            只需要将url设置为/j_spring_security_logout即可,这个地址也是security内部实现了的。
        -->
        <logout invalidate-session="true" logout-success-url="/login.jsp"
                logout-url="/j_spring_security_logout"></logout>

        <custom-filter ref="myUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER"></custom-filter>

        <!--========================新增内容==========start=============================================-->
        <!--替换默认REMEMBER_ME_FILTER-->
        <custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>
        <!--========================新增内容=======================================================-->

        <!--
            通过配置custom-filter来增加过滤器,
            before="FILTER_SECURITY_INTERCEPTOR"表示在Springsecurity默认的过滤器之前执行
        -->
        <custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"></custom-filter>

        <!-- max-sessions只容许一个账号登录,error-if-maximum-exceeded 后面账号登录后踢出前一个账号,
              expired-url session过期跳转界面
              如果concurrency-control标签配置了error-if-maximum-exceeded="true",max-sessions="1",
              那么第二次登录时,是登录不了的。如果error-if-maximum-exceeded="false",
              那么第二次是能够登录到系统的,但是第一个登录的账号再次发起请求时,会跳转到expired-url配置的url中-->
        <session-management session-authentication-error-url="/login.jsp">
            <concurrency-control max-sessions="1" error-if-maximum-exceeded="false"
                                 expired-url="/login.jsp" session-registry-ref="sessionRegistry" />
        </session-management>

        <expression-handler ref="webexpressionHandler" ></expression-handler>

    </http>

    <beans:bean id="loginUrlAuthenticationEntryPoint"
                class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login.jsp"></beans:property>
    </beans:bean>

    <!-- 导入数据源 -->
    <beans:import resource="applicationContext-dataSource.xml"></beans:import>

    <beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
    <!--配置web端使用权限控制-->
    <beans:bean id="webexpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler"/>


    <beans:bean id="bulider" class="cn.quan.ssm.sec.JdbcRequestMapBulider">
        <beans:property name="dataSource" ref="mysqlDataSource"></beans:property>
        <beans:property name="resourceQuery" value="select re.res_string,r.`name` from role r,resc re,resc_role rr where r.id=rr.role_id and re.id=rr.resc_id" />
    </beans:bean>

    <!--配置自定义的过滤器:配置MyUsernamePasswordAuthenticationFilter并将其加入到FilterChain中去-->
    <beans:bean id="myUsernamePasswordAuthenticationFilter"
                class="cn.quan.ssm.sec.dao.MyUsernamePasswordAuthenticationFilter">
        <!--filterProcessesUrl属性为登陆的过滤的地址-->
        <beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
        <!--authenticationManager为authentication-manager标签中配置的-->
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <!--authenticationSuccessHandler为验证成功后跳转的处理器-->
        <beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler" />
        <!--authenticationFailureHandler为验证失败的处理器-->
        <beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler" />
        <!--========================新增内容==========start=============================================-->
        <beans:property name="rememberMeServices" ref="rememberMeServices" />
        <!--========================新增内容==========end=============================================-->
    </beans:bean>

    <beans:bean id="loginLogAuthenticationSuccessHandler"
                class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="targetUrlParameter" value="/index.jsp"></beans:property>
    </beans:bean>

    <beans:bean id="simpleUrlAuthenticationFailureHandler"
                class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/login.jsp"></beans:property>
    </beans:bean>


    <beans:bean id="filterSecurityInterceptor"
                class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <!-- 用户拥有的权限 -->
        <beans:property name="accessDecisionManager" ref="accessDecisionManager" />
        <!-- 用户是否拥有所请求资源的权限 -->
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <!-- 资源与权限对应关系 -->
        <beans:property name="securityMetadataSource" ref="securityMetadataSource" />
    </beans:bean>

    <!--授权管理器:acl领域模型-->
    <beans:bean id="accessDecisionManager" class="cn.quan.ssm.sec.dao.MyAccessDecisionManager" />

    <!--认证管理-->
    <authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="userDetailsService">
        </authentication-provider>
        <!--========================新增内容==========start=============================================-->
        <!-- 记住密码 -->
        <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider>
        <!--========================新增内容==========end=============================================-->
    </authentication-manager>

    <!--========================新增内容==========start=============================================-->
    <!--
        说明:dataSource就是连接数据库的数据源;usersByUsernameQuery就是配置jdbc-user-service时候的users-by-username-query,
        这个是根据用户名来查询用户的sql语句;同理authoritiesByUsernameQuery就是对应的authorities-by-username-query,
        这个用来根据用户名查询对应的权限。
    -->
    <!-- 配置userDetailsService -->

    <beans:bean id="userDetailsService"
                class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
        <beans:property name="usersByUsernameQuery"
                        value="select username,password,status as enabled from user where username = ?" />
        <beans:property name="authoritiesByUsernameQuery"
                        value="select user.username,role.name from user,role,user_role
                                    where user.id=user_role.user_id and
                                    user_role.role_id=role.id and user.username=?" />
        <beans:property name="dataSource" ref="mysqlDataSource" />
    </beans:bean>

    <!--配置rememberMe的过滤器: Remember-Me 对应的 Filter-->
    <beans:bean id="rememberMeFilter"
                class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
        <beans:property name="rememberMeServices" ref="rememberMeServices" />
        <beans:property name="authenticationManager" ref="authenticationManager" />
    </beans:bean>
    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~基于简单加密的方式~~start~~~~~~不存数据库~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!-- rememberService -->
    <!-- RememberMeServices 的实现 -->
    <beans:bean id="rememberMeServices"
                class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
        <beans:property name="userDetailsService" ref="userDetailsService" />
        <beans:property name="key" value="zmc" />
        <!-- 指定 request 中包含的用户是否选择了记住我的参数名 -->
        <beans:property name="parameter" value="rememberMe"/>
    </beans:bean>
    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~基于简单加密的方式~~end~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->


    <!-- 记住密码 -->
    <!-- key 值需与对应的 RememberMeServices 保持一致 -->
    <beans:bean id="rememberMeAuthenticationProvider"
                class="org.springframework.security.authentication.RememberMeAuthenticationProvider" >
        <beans:property name="key" value="zmc" />
    </beans:bean>
    <!--========================新增内容==========end=============================================-->

    <!--自定义的切入点-->
    <beans:bean id="securityMetadataSource"
                class="cn.quan.ssm.sec.dao.MyFilterInvocationSecurityMetadataSource">
        <beans:property name="bulider" ref="bulider"></beans:property>
    </beans:bean>

</beans:beans>
因为这个例子是在之前的例子上进行修改的,所以其它的没讲到的一些配置在之前博客都有详细的讲解,请参考之前的博客。
二 基于持久化的方式配置
在将这配置之前先对上面rememberService中的实现类TokenBasedRememberMeServices进行简单的讲解,该类主要是基于简单加密 token 的一个实现类。TokenBasedRememberMeServices 会在用户选择了记住我成功登录后,生成一个包含 token 信息的 cookie 发送到客户端;如果用户登录失败则会删除客户端保存的实现 Remember-Me 的 cookie。需要自动登录时,它会判断 cookie 中所包含的关于 Remember-Me 的信息是否与系统一致,一致则返回一个 RememberMeAuthenticationToken 供 RememberMeAuthenticationProvider 处理,不一致则会删除客户端的 Remember-Me cookie。TokenBasedRememberMeServices 还实现了 Spring Security 的 LogoutHandler 接口,所以它可以在用户退出登录时立即清除 Remember-Me cookie。
而基础持久化方式配置的实质就是这个类不同,基于持久化方式配置的所用的实现类为:PersistentTokenBasedRememberMeServices,一看名字就知道其作用,就是将token进行持久化保存起来,要保存数据相应的就是为其制定保存的地方,这个保存的地方就是用PersistentTokenRepository来指定的,Spring Security 对此有两种实现,InMemoryTokenRepositoryImpl 和 JdbcTokenRepositoryImpl。前者是将 token 存放在内存中的,通常用于测试,而后者是将 token 存放在数据库中。PersistentTokenBasedRememberMeServices 默认使用的是前者,我们可以通过其 tokenRepository 属性来指定使用的 PersistentTokenRepository。这例子用JdbcTokenRepositoryImpl来进行持久化保存,显然要往数据库保存数据,肯定要有一张表,这个表security也有提供,sql语句为:create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)。在数据库中创建该表即可。
创建后的表为:

所以持久化方式配置只需要将第一种方式的TokenBasedRememberMeServices进行修改就可以,用以下代码提换就可以了:
<beans:bean id="rememberMeServices"
                class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
        <beans:property name="userDetailsService" ref="userDetailsService" />
        <beans:property name="key" value="zmc" />
        <!-- 指定 request 中包含的用户是否选择了记住我的参数名 -->
        <beans:property name="parameter" value="rememberMe" />
        <beans:property name="tokenRepository">
            <beans:bean class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
                <!-- 数据源 -->
                <beans:property name="dataSource" ref="mysqlDataSource"/>
                <!-- 是否在启动时创建持久化 token 的数据库表 若为true,但数据有这个表时,会启动失败,提示表已存在 -->
                <beans:property name="createTableOnStartup" value="false"/>
            </beans:bean>
        </beans:property>
    </beans:bean>
三 两种配置的效果
没点击2周不用登陆的时候,登陆后再退出,再访问资源的时候就要求重新登陆。同时数据库也不会新增数据。
当勾选2周不用登陆在登陆后,就算退出登陆后,再访问资源也可以不用直接访问,同时,数据库也会将登陆信息保存起来,比较两次数据还可以发现,除了用户名没变,其他的数据都会因为第二次访问而进行更新。

特别说明:
相关源码的下载可以到http://download.csdn.net/download/zsq520520/9968528进行下载。项目结构详情如下:

该资源是Spring Security实战七篇文档中组织的源码,详情如下:
ssecurity项目是Spring Security实战(一和二)的源码;
ssecurity-db项目是Spring Security实战(三)的源码;
ssceurity-page项目是Spring Security实战(四)的源码;
ssecurity-pageClass项目是Spring Security实战(五)的源码;
ssecurity-customFilter项目是Spring Security实战(六)的源码;
ssecurity-rememberMe项目是Spring Security实战(七)的源码;
本人开发工具是IDEA,每个项目中的代码均可以运行并测试。Eclipse也是一样可以运行的。欢迎下载学习,如有问题,多多指教!

猜你喜欢

转载自blog.csdn.net/zsq520520/article/details/77881977