1. 程式人生 > >Spring Security教程(六):自定義過濾器進行認證處理

Spring Security教程(六):自定義過濾器進行認證處理

這裡接著上篇的自定義過濾器,這裡主要的是配置自定義認證處理的過濾器,並加入到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 com.zmc.demo;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; 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.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.util.StringUtils; /** * @classname MyUsernamePasswordAuthenticationFilter * @author ZMC * @time 2017-1-13 * */ public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { public static final String USERNAME = "j_username"; public static final String PASSWORD = "j_password"; /** * @Description:使用者登入驗證方法入口 * @param :args * @return * @throws Exception */ @Override 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); // 加密密碼(根據“密碼{使用者名稱})進行加密 // String sh1Password = password + "{" + username + "}"; // PasswordEncoder passwordEncoder = new // StandardPasswordEncoderForSha1(); // String result = passwordEncoder.encode(sh1Password); // UserInfo userDetails = (UserInfo) // userDetailsService.loadUserByUsername(username); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } /** * @Description:獲取密碼 * @param :args * @return * @throws Exception */ @Override protected String obtainPassword(HttpServletRequest request) { // TODO Auto-generated method stub Object obj = request.getParameter(PASSWORD); return null == obj ? "" : obj.toString(); } /** * @Description:獲取使用者名稱 * @param :args * @return * @throws Exception */ @Override protected String obtainUsername(HttpServletRequest request) { // TODO Auto-generated method stub Object obj = request.getParameter(USERNAME); return null == obj ? "" : obj.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"
    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="/login.jsp" security="none"></http>
    <http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <!-- <form-login login-page="/login.jsp" default-target-url="/index.jsp"
            authentication-failure-url="/login.jsp?error=true" /> -->
        <logout invalidate-session="true" logout-success-url="/login.jsp"
            logout-url="/j_spring_security_logout" />
        <custom-filter ref="myUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER" />
        <!-- 通過配置custom-filter來增加過濾器,before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity預設的過濾器之前執行。 -->
        <custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
    </http>
    <beans:bean id="loginUrlAuthenticationEntryPoint"
        class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login.jsp" />
    </beans:bean>
    
    <!-- 資料來源 -->
    <beans:bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        <!-- 此為c3p0在spring中直接配置datasource c3p0是一個開源的JDBC連線池 -->
        <beans:property name="driverClass" value="com.mysql.jdbc.Driver" />
        <beans:property name="jdbcUrl"
            value="jdbc:mysql://localhost:3306/springsecuritydemo?useUnicode=true&characterEncoding=UTF-8" />
        <beans:property name="user" value="root" />
        <beans:property name="password" value="" />
        <beans:property name="maxPoolSize" value="50"></beans:property>
        <beans:property name="minPoolSize" value="10"></beans:property>
        <beans:property name="initialPoolSize" value="10"></beans:property>
        <beans:property name="maxIdleTime" value="25000"></beans:property>
        <beans:property name="acquireIncrement" value="1"></beans:property>
        <beans:property name="acquireRetryAttempts" value="30"></beans:property>
        <beans:property name="acquireRetryDelay" value="1000"></beans:property>
        <beans:property name="testConnectionOnCheckin" value="true"></beans:property>
        <beans:property name="idleConnectionTestPeriod" value="18000"></beans:property>
        <beans:property name="checkoutTimeout" value="5000"></beans:property>
        <beans:property name="automaticTestTable" value="t_c3p0"></beans:property>
    </beans:bean>
    
    <beans:bean id="builder" class="com.zmc.demo.JdbcRequestMapBulider"> 
        <beans:property name="dataSource" ref="dataSource" /> 
        <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>
    
    <beans:bean id="myUsernamePasswordAuthenticationFilter"
        class="com.zmc.demo.MyUsernamePasswordAuthenticationFilter
        ">
        <beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="authenticationSuccessHandler"
            ref="loginLogAuthenticationSuccessHandler" />
        <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:bean>
 
    <beans:bean id="simpleUrlAuthenticationFailureHandler"
        class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/login.jsp" />
    </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 class="com.zmc.demo.MyAccessDecisionManager" id="accessDecisionManager">
    </beans:bean>
    <!--  -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider>
            <jdbc-user-service data-source-ref="dataSource"
                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="com.zmc.demo.MyFilterInvocationSecurityMetadataSource">
        <beans:property name="builder" ref="builder"></beans:property>
    </beans:bean>
    
</beans:beans>

三、結果

因為處理驗證的過濾器不一樣,其他的和教程五一樣,結果這裡就不展示了,參考前面的教程。