研究了好长时间,不知道从哪里下手。新的版本,很多东西在网上找不到,只能看他们的文档,当然这些文档相当不错,就看是否耐心的研究了!总是有急躁的心理作祟,不能专心研读,却处处碰壁,效率上反而未达预期效果!
终于,在无数次的沮丧下,稍微看到了点光亮!前面的文章太过皮毛,接下来的一些,希望能更加实际的,更加深入的分析每一个过程!
一直通过默认配置进行设置:
namespace(是security 3.0,网上也看到一些兄弟描述的是3.0,但是总是不符合我这里的namespace配置):
<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/security 
               http://www.springframework.org/schema/security/spring-security-3.0.xsd">
按照默认配置的http(这是用来根据namespace设置的基本的security过滤器chain):
auto-config=true时,就相当于
  <http>
    <form-login />
    <http-basic />
    <logout />
  </http>
也就是使用了默认的过滤器。
我最开始的想法是能够把本地的login信息(不是调用spring security的login方法),传入到spring security的验证过滤器里面。
这里有一个比较关键的问题,就是封装他们的过滤器(或者仅仅是知道他们到底是哪些过滤器在起作用):
表1
    
        
            | 
                
                    
                        | Alias | Filter Class | Namespace Element or
                        Attribute |  
                        | CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |  
                        | CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | session-management/concurrency-control |  
                        | SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |  
                        | LOGOUT_FILTER | LogoutFilter | http/logout |  
                        | X509_FILTER | X509AuthenticationFilter | http/x509 |  
                        | PRE_AUTH_FILTER | AstractPreAuthenticatedProcessingFilterSubclasses | N/A |  
                        | CAS_FILTER | CasAuthenticationFilter | N/A |  
                        | FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |  
                        | BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |  
                        | SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareFilter | http/@servlet-api-provision |  
                        | REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |  
                        | ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |  
                        | SESSION_MANAGEMENT_FILTER | SessionManagementFilter | session-management |  
                        | EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |  
                        | FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |  
                        | SWITCH_USER_FILTER | SwitchUserFilter | N/A |  | 
 | 
 | 
    
(最开始看的时候,把这个表格忽略了,现在看来这些就是我们想要的!)
我们的验证过程,就是按照这样的顺序进行的。自上而下进行。
如果我们要自己定制相应的验证处理方法(在过滤器里面),我们就可以对照上面的过滤器,覆盖相应的接口方法。
根据我的处理过程,主要是用户名密码的验证过程,我大体描述一下自己的配置和处理过程:
1.配置namespace的标签:
  <http  use-expressions="true" ><!-- This is not the default value -->
       <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter"/> <!--This is my own filter which just extends AbstractAuthenticationProcessingFilter as what UsernamePasswordAuthenticationFilter does.-->
       <intercept-url pattern="/test/**"  access="hasRole('ROLE_MY')"/><!-- I tested that what is role,and how I can use it.So ROLE_MY is just a role name I defined.-->
       <intercept-url pattern="/login.jsp*" access="permitAll" />
       <logout />
       <anonymous /> 
       <http-basic />
  </http>
这里的问题是,要定制自己的过滤器,就要通过<custom-filter/>,然后对照 表1 中指定的position,覆盖默认的filter。
2.我的form-login  filter配置(这些配置都是在application-security.xml文件中)为:
<beans:bean id="myFilter" 
      class="com.saveworld.authentication.filters.MyUsernamePasswordAuthenticationFilter">
    <beans:property name="defaultTargetUrl"  value="/default.jsp" />
    <beans:property name="defaultFailureUrl"  value="/error.jsp" />
    <beans:property name="authenticationManager" ref="authenticationManager" /> 
    <beans:property name="filterProcessesUrl" value="/j_spring_security_check" /> 
    <beans:property name="continueChainBeforeSuccessfulAuthentication" value="false" /> 
  </beans:bean>
NOTE:
在这里有个问题就是: filter position conflicts!
如果使用这样的配置
<http auto-config='true'>
   <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter"/>
</http>
自定义的filter对应的position是FORM_LOGIN_FILTER
但是因为使用了auto-config='true',所以默认有<form-login />,which is on the position FORM_LOGIN_FILTER!
这时就会出现position conflicts问题了。当然,如果你没有设置auto-config='true',但是却自己设置了<form-login />,呵呵,这个情况就是自己大意了,还是有了position conflicts的异常,所以,好好看看上面的表格是相当必要的,看清楚每个position默认都对应那些namespace,都是对应的哪些filter!
接着:
我的类MyUsernamePasswordAuthenticationFilter实现(我的说明方式就按照哪里需要,哪里加入的方式了):
package com.saveworld.authentication.filters;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.NullRememberMeServices;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.TextEscapeUtils;
import org.springframework.util.Assert;
import com.saveworld.authentication.handlers.MySavedRequestAwareAuthenticationSuccessHandler;
import com.saveworld.authentication.handlers.MySimpleUrlAuthenticationFailureHandler;
public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter{
    //~ Static fields/initializers =====================================================================================
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME";
    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;
    
    private boolean allowSessionCreation = true;
    
    private String defaultTargetUrl = "/";
    private String defaultFailureUrl = "/login.jsp";
    
    private AuthenticationSuccessHandler successHandler = null;
    private AuthenticationFailureHandler failureHandler = null;
    
    
    private RememberMeServices rememberMeServices = null;
    
    //~ Constructors ===================================================================================================
    public MyUsernamePasswordAuthenticationFilter() {
        //初始化
        super("/j_spring_security_check");
        this.rememberMeServices = (super.getRememberMeServices() == null) 
                                     ? new NullRememberMeServices():super.getRememberMeServices();
        
    }
    //~ Methods ========================================================================================================
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        // Place the last username attempted into HttpSession for views
        HttpSession session = request.getSession(false);
        if (session != null || getAllowSessionCreation()) {
            request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));
        }
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    
    
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
            Authentication authResult) throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
        }
        
        SecurityContextHolder.getContext().setAuthentication(authResult);
        rememberMeServices.loginSuccess(request, response, authResult);
        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        if(successHandler == null){
            successHandler = new MySavedRequestAwareAuthenticationSuccessHandler(getDefaultTargetUrl());
        }
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }
    
    public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException failed) throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        
        if(failureHandler == null){
            failureHandler = new MySimpleUrlAuthenticationFailureHandler(getDefaultFailureUrl());
        }
        
        if (logger.isDebugEnabled()) {
            logger.debug("Authentication request failed: " + failed.toString());
            logger.debug("Updated SecurityContextHolder to contain null Authentication");
            logger.debug("Delegating to authentication failure handler" + failureHandler);
        }
        HttpSession session = request.getSession(false);
        if (session != null || allowSessionCreation) {
            request.getSession().setAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
        }
        rememberMeServices.loginFail(request, response);
        failureHandler.onAuthenticationFailure(request, response, failed);
    }
    
    
    /**
     * Enables subclasses to override the composition of the password, such as by including additional values
     * and a separator.<p>This might be used for example if a postcode/zipcode was required in addition to the
     * password. A delimiter such as a pipe (|) should be used to separate the password and extended value(s). The
     * <code>AuthenticationDao</code> will need to generate the expected password in a corresponding manner.</p>
     *
     * @param request so that request attributes can be retrieved
     *
     * @return the password that will be presented in the <code>Authentication</code> request token to the
     *         <code>AuthenticationManager</code>
     */
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(passwordParameter);
    }
    /**
     * Enables subclasses to override the composition of the username, such as by including additional values
     * and a separator.
     *
     * @param request so that request attributes can be retrieved
     *
     * @return the username that will be presented in the <code>Authentication</code> request token to the
     *         <code>AuthenticationManager</code>
     */
    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(usernameParameter);
    }
    /**
     * Provided so that subclasses may configure what is put into the authentication request's details
     * property.
     *
     * @param request that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details set
     */
    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
    /**
     * Sets the parameter name which will be used to obtain the username from the login request.
     *
     * @param usernameParameter the parameter name. Defaults to "j_username".
     */
    public void setUsernameParameter(String usernameParameter) {
        Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
        this.usernameParameter = usernameParameter;
    }
    /**
     * Sets the parameter name which will be used to obtain the password from the login request..
     *
     * @param passwordParameter the parameter name. Defaults to "j_password".
     */
    public void setPasswordParameter(String passwordParameter) {
        Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
        this.passwordParameter = passwordParameter;
    }
    /**
     * Defines whether only HTTP POST requests will be allowed by this filter.
     * If set to true, and an authentication request is received which is not a POST request, an exception will
     * be raised immediately and authentication will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
     * will be called as if handling a failed authentication.
     * <p>
     * Defaults to <tt>true</tt> but may be overridden by subclasses.
     */
    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }
    public final String getUsernameParameter() {
        return usernameParameter;
    }
    public final String getPasswordParameter() {
        return passwordParameter;
    }
    public String getDefaultTargetUrl() {
        return defaultTargetUrl;
    }
    public void setDefaultTargetUrl(String defaultTargetUrl) {
        this.defaultTargetUrl = defaultTargetUrl;
    }
    public String getDefaultFailureUrl() {
        return defaultFailureUrl;
    }
    public void setDefaultFailureUrl(String defaultFailureUrl) {
        this.defaultFailureUrl = defaultFailureUrl;
    }
    
    
}
这里要关注的就是几个字段:
    <beans:property name="defaultTargetUrl"  value="/default.jsp" />
    <beans:property name="defaultFailureUrl"  value="/error.jsp" />
这两个字段是指定验证成功或失败后转向的页面,这里要注意是以“/”开头,否则在AbstractAuthenticationTargetUrlRequestHandler中调用setDefaultTargetUrl方法时会抛出"defaultTarget must start with '/' or with 'http(s)'"的异常!
默认情况下,FORM_LOGIN_FILTER对应的target url和failure url都是通过 <form-login />中的
default-target-url,authentication-failure-url指定,也可以通过指定authentication-success-handler-ref和authentication-failure-handler-ref来实现认证成功和失败之后的处理方式.在我的filter中,是自定义了两个handler分别对应成功的和失败的验证。
3.用户信息获取和验证:
  <authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref='myUserDetailsService'/>
  </authentication-manager>
  <beans:bean id="myUserDetailsService" class="com.saveworld.userdetails.MyUserDetailsService"></beans:bean> 
这个指定的authentication-manager是使用默认的ProviderManager,这个manager是在哪里使用的呢?
看看MyUsernamePasswordAuthenticationFilter中的attemptAuthentication方法的最后一行,这里是获取指定的authentication-manager。getAuthenticationManager是从父类AbstractAuthenticationProcessingFilter继承过来的。所以,我们的<authentication-manager alias="authenticationManager">中就指定了一个别名authenticationManager,在myfilter中设置属性的引用<beans:property name="authenticationManager" ref="authenticationManager" />,然后我们就可以通过Provider引用我们自己的用户信息验证service了(eg:用户信息获取和验证)!这里实际是使用了Method Template模式(AbstractAuthenticationManager中设定模板函数doAuthentication,ProviderManager中做了实现)。
这里难免要说明一下,我们的Service是如何被调用的,我们做了配置<authentication-provider user-service-ref='myUserDetailsService'/>
指定了我们的UserDetailsService,类实现(为了测试和理解,The easier the better!):
package com.saveworld.userdetails;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class MyUserDetailsService implements UserDetailsService{
    
    private HashMap<String, User> userDetails = new HashMap<String, User>();
    private HashMap<String, List<GrantedAuthority>> userAuthorities = new HashMap<String, List<GrantedAuthority>>(); 
    
    public MyUserDetailsService(){
        //Make up a user named 'rod' with 'rod' as his password!
        //
        String username = "rod";
        String password = "1";
        boolean enabled = true;
        //purview for rod
        GrantedAuthority specAuth = new GrantedAuthorityImpl("ROLE_MY");
        List<GrantedAuthority> rodsAuthsList = new ArrayList<GrantedAuthority>();
        rodsAuthsList.add(specAuth);
//        userAuthorities.put("rod", rodsAuthsList);
        userDetails.put("rod", new User(username, password, enabled, true, true, true, rodsAuthsList));
    }
    
    
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException, DataAccessException {
        System.out.println("验证从此地过了一遭");
        return userDetails.get(username);
    }
}
通过DaoAuthenticationProvider中的userDetailsService关联我们的UserDetailsService(不得不提的是,AbstractUserDetailsAuthenticationProvider中有设定了模板函数retrieveUser,DaoAuthenticationProvider进行了实现,通过retrieveUser方法调用UserDetailsService.loadUserByUsername,然后在AbstractUserDetailsAuthenticationProvider.authenticate方法进行验证)。
接下来就是看验证的结果了,是否成功,进入filter chain中。
这一切就这么有条不紊的进行了!呵呵,总算是有点成果了!有了一点点感性的认识了!上面的描述中难免会有些混乱,但是尽量是哪里需要,哪里就做说明!
下篇中说明entrypoint的用处吧!