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

版权声明:【本文为博主原创文章或收集整理,未经博主允许不得转载】 https://blog.csdn.net/zsq520520/article/details/77881881
这里接着上篇的自定义过滤器,这里主要的是配置自定义认证处理的过滤器,并加入到FilterChain的过程。在我们自己不在xml做特殊的配置情况下,security默认的做认证处理的过滤器为UsernamePasswordAuthenticationFilter,通过查看源码知道,做认证处理的方法为attemptAuthentication,这个方法的主要作用就是将用户输入的账号和密码,封装成一个UsernamePasswordAuthenticationToken对象,然后通过setDetails方法将这个对象储存起来,然后调用this.getAuthenticationManager().authenticate(authRequest)方法返回一个Authentication对象。其中这个过程this.getAuthenticationManager().authenticate(authRequest)又调用的其他的许多类,这里简单的讲解下:
UsernamePasswordAuthenticationFilter-->ProviderManager-->AbstractUserDetailsAuthenticationProvider-->DaoAuthenticationProvider-->JdbcDaoImpl
当输入用户名和密码后,点击登陆到达UsernamePasswordAuthenticationFilter的attemptAuthentication方法,这个方法是登陆的入口,然后其调用ProviderManager中的authenticate方法,而ProviderManager委托给AbstractUserDetailsAuthenticationProvider的authenticate做,然后AbstractUserDetailsAuthenticationProvider又调用DaoAuthenticationProvider中的retrieveUser,在DaoAuthenticationProvider类的retrieveUser方法中,因为要通过输入的用户名获取到一个UserDetails,所以其调用JdbcDaoImpl中的loadUserByUsername方法,该方法给它的调用者返回一个查询到的用户(UserDetails),最终AbstractUserDetailsAuthenticationProvider的authenticate方法中会得到一个UserDetails对象user,然后接着执行preAuthenticationChecks.check(user)和additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);其中前面这个方法是判断,查询的用户是否可用或被锁等,后面的则是判断查询到的user对象的密码是否和authentication(这个对象其实就是存储用户输入的用户名和密码)的密码一样,若一样则表示登陆成功,若错误,则throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);Bad credentials这个消息就是登陆失败后的信息。初步的讲解了登陆过程中类的调用,那么下面这个例子就是自定义一个MyUsernamePasswordAuthenticationFilter来代替默认的  UsernamePasswordAuthenticationFilter。
一 自定义MyUsernamePasswordAuthenticationFilter
这个类可以继承UsernamePasswordAuthenticationFilter,然后重写attemptAuthentication方法,这个方法是登陆的入口方法。
package cn.quan.ssm.sec.dao;

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 配置自定义认证处理的过滤器,并加入到FilterChain的过程。
 * 将自定义的Filter加入到security中的FilterChain中去
 * 其实和默认的UsernamePasswordAuthenticationFilter并没有什么区别,
 * 但是这里主要是学会将自定义的Filter加入到security中的FilterChain中去,实际上这个方法中,
 * 一般会直接验证用户输入的和通过用户名从数据库里面查到的用户的密码是否一致,如果不一致,就抛异常,否则继续向下执行。
 * @auther zhangsq on 2017-9-6.
 */

public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private static final String USERNAME = "j_username";
    private static final String PASSWORK = "j_password";

    /**
     * 用户登录验证方法入口
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if(!request.getMethod().equals("POST")){
            throw new AuthenticationServiceException("Authentication method not supported:"+request.getMethod());
        }
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        if(null == username){
            username = "";
        }
        if(null == password){
            password = "";
        }
        username = username.trim();

        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username,password);
        setDetails(request,authenticationToken);
        return this.getAuthenticationManager().authenticate(authenticationToken);
    }

    /**
     * 获取密码
     * @param request
     * @return
     */
    protected String obtainPassword(HttpServletRequest request){
        Object obj = request.getParameter(PASSWORK);
        return null == obj ? "" : obj.toString();
    }

    /**
     * 获取用户名
     * @param request
     * @return
     */
    protected String obtainUsername(HttpServletRequest request){
        Object object = request.getParameter(USERNAME);
        return null == object ? "" : object.toString().trim().toLowerCase();
    }

}
上述的代码这样写其实和默认的UsernamePasswordAuthenticationFilter并没有什么区别,但是这里主要是学会将自定义的Filter加入到security中的FilterChain中去,实际上这个方法中,一般会直接验证用户输入的和通过用户名从数据库里面查到的用户的密码是否一致,如果不一致,就抛异常,否则继续向下执行。
二 配置MyUsernamePasswordAuthenticationFilter并将其加入到FilterChain中去
MyUsernamePasswordAuthenticationFilter有filterProcessesUrl属性为登陆的过滤的地址,authenticationManager为authentication-manager标签中配置的东西,authenticationSuccessHandler为验证成功后跳转的处理器,authenticationFailureHandler为验证失败的处理器。另外还要配置一个出登陆引导的处bean:LoginUrlAuthenticationEntryPoint
配置代码如下:
<?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>
        <!--========================新增内容==========start=============================================-->
        <custom-filter ref="myUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER"></custom-filter>
        <!--========================新增内容==========end=============================================-->
        <!--
            通过配置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>
    <!--========================新增内容==========start=============================================-->
    <beans:bean id="loginUrlAuthenticationEntryPoint"
                class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login.jsp"></beans:property>
    </beans:bean>
    <!--========================新增内容==========end=============================================-->

    <!-- 导入数据源 -->
    <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:property>
    </beans:bean>
    <!--========================新增内容==========start=============================================-->
    <!--配置自定义的过滤器:配置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" />
    </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>
    <!--========================新增内容=========end==============================================-->


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

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

    <!--认证管理-->
    <authentication-manager alias="authenticationManager">
        <authentication-provider>
            <jdbc-user-service data-source-ref="mysqlDataSource"
                  users-by-username-query="select username,`password`,`status` as enabled from `user` where username = ?"
                  authorities-by-username-query="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 = ?" />

        </authentication-provider>
    </authentication-manager>

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

</beans:beans>
其他的一些配置在教程五有详细的讲解。
三 结果
因为处理验证的过滤器不一样,其他的和实战五一样,结果这里就不展示了。

猜你喜欢

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