spring security 源码解读 1 spring security 源码解读 1

http://feiyan35488.iteye.com/blog/896733

这一阵子看到了security,很感兴趣。于是研究一下,我在javaeye上查了好多相关的文档,收益匪浅,从入门级的配置问题,到源码级的解读都非常不错,但是还要自己在亲自走一遍流程才踏实。

 

我看的security 3.0的源码,原因是 security 2.0 的源码没办法通过maven获取到 。

 

首先 security的控制内容有: url,method,session三种,我项目中用到的只有 url。下面就按url的流程来走。

思路:  使用filter,过滤所有的url 如 /* 这样,并且这个filter应在最前面,道理就不到说了吧。

     1》 security使用的 filter是 org.springframework.web.filter.DelegatingFilterProxy类,在spring-web jar中。

        @Override

Java代码    收藏代码
  1. protected void initFilterBean() throws ServletException {  
  2.     // If no target bean name specified, use filter name.  
  3.     <span style="color: #ff0000;">if (this.targetBeanName == null) {  
  4.         this.targetBeanName = getFilterName();  
  5.     }</span>  
  6.   
  7.     // Fetch Spring root application context and initialize the delegate early,  
  8.     // if possible. If the root application context will be started after this  
  9.     // filter proxy, we'll have to resort to lazy initialization.  
  10.     synchronized (this.delegateMonitor) {  
  11.         WebApplicationContext wac = findWebApplicationContext();  
  12.         if (wac != null) {  
  13.             this.delegate = initDelegate(wac);  
  14.         }  
  15.     }  
  16. }  
  17.   
  18. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)  
  19.         throws ServletException, IOException {  
  20.   
  21.     // Lazily initialize the delegate if necessary.  
  22.     Filter delegateToUse = null;  
  23.     synchronized (this.delegateMonitor) {  
  24.         if (this.delegate == null) {  
  25.             WebApplicationContext wac = findWebApplicationContext();  
  26.             if (wac == null) {  
  27.                 throw new IllegalStateException("No WebApplicationContext found:  
  28.                                                no ContextLoaderListener registered?");  
  29.             }  
  30.             this.delegate = initDelegate(wac);  
  31.                                     // 该方法中的 代码  
  32.                                    //   Filter delegate =<span style="color: #ff0000;"> wac.getBean(getTargetBeanName(), Filter.class);</span>  
  33.         }  
  34.         delegateToUse = this.delegate;  
  35.     }  
  36.   
  37.     // Let the delegate perform the actual doFilter operation.  
  38.     invokeDelegate(delegateToUse, request, response, filterChain);  
  39. }  

 

  这里需要注意一点 filter-name 必须为 springSecurityFilterChain,从DelegatingFilterProxy这个名字中可以猜到这只是个代理类(确实如此),当这个类执行时会去取得真正的filter类,这个类在spring容器中默认生成id为 springSecurityFilterChain,在3.0中 该filter 添加了一个 targetName 字段,可以从上面红色代码部分看到它的作用,因此可以通过指定targetName字段,来防止和项目中的其他filter冲突。

 

   接下来 该真正的 filterChain出场了,这个类是security事务相关的,应该在security包中。

于是 在 spring-security-web jar中发现了这个类:org.springframeword.security.web.FilterChainProxy ,进去看看可以看出这个就是我要找的类。(关于这一点我是从命名上看出来的,bean的id要和类名保持一致)。

 

 下班了 ,回去再继续添加。

==========================华丽丽的分界线====================================

 

吃完饭 ,继续。

FilterChainProxy 代码:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

     throws IOException, ServletException {

 

        FilterInvocation fi = new FilterInvocation(request, response, chain);

        List<Filter> filters = getFilters(fi.getRequestUrl());

                            // 根据url 得到需要经过的filters

                            // 这里不是很明白,有知道的同学可以留言。

 

        if (filters == null || filters.size() == 0) { // 如果没有合适的 ,就继续进行filter

            if (logger.isDebugEnabled()) {

                logger.debug(fi.getRequestUrl() +

                        filters == null ? " has no matching filters" : " has an empty filter list");

            }

 

            chain.doFilter(request, response);

 

            return;

        }

        //如果有filter 就进行虚拟的filter链。这里并没有跳出容器的 filter链,

        // 当这个虚拟的filter链完成之后,就继续进行 容器的filter

        VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);

        virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());

    }

 接下来就该进行 filterChain了,在security中有好多的filter:

  CHANNEL_FILTER ChannelProcessingFilter 

CONCURRENT_SESSION_FILTER ConcurrentSessionFilter 

SESSION_CONTEXT_INTEGRATION_FILTER HttpSessionContextIntegrationFilter 

LOGOUT_FILTER LogoutFilter 

X509_FILTER X509PreAuthenticatedProcessigFilter 

PRE_AUTH_FILTER Subclass of AstractPreAuthenticatedProcessingFilter 

CAS_PROCESSING_FILTER CasProcessingFilter 

AUTHENTICATION_PROCESSING_FILTER AuthenticationProcessingFilter 

BASIC_PROCESSING_FILTER BasicProcessingFilter 

SERVLET_API_SUPPORT_FILTER classname 

REMEMBER_ME_FILTER RememberMeProcessingFilter 

ANONYMOUS_FILTER AnonymousProcessingFilter 

EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter 

NTLM_FILTER NtlmProcessingFilter 

FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor 

SWITCH_USER_FILTER SwitchUserProcessingFilter 。

 

下面我只分析了 AuthenticationProcessingFilter,这是登录认证处理filter

 public UsernamePasswordAuthenticationFilter() {

        super("/j_spring_security_check");// 这就是 登录验证的 url。

    }

 

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        if (postOnly && !request.getMethod().equals("POST")) {

            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());

        }  // 只允许以post方法 进行认证,能防止一些简单的破解

 

        String username = obtainUsername(request);

        String password = obtainPassword(request);

 

        if (username == null) {

            username = "";

        }

 

        if (password == null) {

            password = "";

        }

 

        username = username.trim();

        // 只是将 username和password封装进去

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

        // 取得AuthenticationManager 进行认证

        return this.getAuthenticationManager().authenticate(authRequest);

    }

 

    从request中取得 username,password,封装进 UsernamePasswordAuthenticationToken 中,

    然后将username中写到 session中,这里对username去掉了首尾的空格

    然后调用 AuthenticationManager的 authenticate方法进行具体的认证操作。

 

     public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {

        Class<? extends Authentication> toTest = authentication.getClass();

        AuthenticationException lastException = null;

        Authentication result = null;

 

        for (AuthenticationProvider provider : getProviders()) {

 

            if (!provider.supports(toTest)) {

                continue;

            }

 

            logger.debug("Authentication attempt using " + provider.getClass().getName());

 

            try {

                result = provider.authenticate(authentication);

 

                if (result != null) {

                    copyDetails(authentication, result);

                    break;

                }

            } catch (AccountStatusException e) {

                // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status

                eventPublisher.publishAuthenticationFailure(e, authentication);

                throw e;

            } catch (AuthenticationException e) {

                lastException = e;

            }

        }

 

        if (result == null && parent != null) {

            // Allow the parent to try.

            try {

                result = parent.authenticate(authentication);

            } catch (ProviderNotFoundException e) {

                // ignore as we will throw below if no other exception occurred prior to calling parent and the parent

                // may throw ProviderNotFound even though a provider in the child already handled the request

            } catch (AuthenticationException e) {

                lastException = e;

            }

        }

 

        if (result != null) {

            if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {

                // Authentication is complete. Remove credentials and other secret data from authentication

                ((CredentialsContainer)result).eraseCredentials();

            }

 

            eventPublisher.publishAuthenticationSuccess(result);

            return result;

        }

 

        // Parent was null, or didn't authenticate (or throw an exception).

 

        if (lastException == null) {

            lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",

                        new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));

        }

 

        eventPublisher.publishAuthenticationFailure(lastException, authentication);

 

        throw lastException;

    }

 

     这里对 用户进行认证,成功就发布成功事件,并返回。 失败就发布失败事件,并返回exception

    这里具体的认证过程还是不大熟悉,等再详细的看明白了 再细说。

     看明白了,authenticationManager可以有多个 provider如 默认的daoAuthenticationProvider 和  JaasAuth ,RememmberMeAuth 等

     下面说 daoAuthenticationProvider 中可以有  UserDetailsService ,

 

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)

            throws AuthenticationException {

        UserDetails loadedUser;

 

        try {

            loadedUser = this.getUserDetailsService().loadUserByUsername(username);

        }

        catch (DataAccessException repositoryProblem) {

            throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);

        }

 

        if (loadedUser == null) {

            throw new AuthenticationServiceException(

                    "UserDetailsService returned null, which is an interface contract violation");

        }

        return loadedUser;

    }

又通过 userDetailsService.loadUserByUsername()  ,当不存在时 ,返回null

    来得到 UserDetails 这样就能把 整个认证过程理顺了

 

   总结一下,  security 的认证过程能理顺了,对其衔接的过渡代码 还有些拿不准,还有 取得filters的 规则还不是很清楚,还要继续看下去。

   已经能理顺了, 在BeanId 类中发现了那些默认的字符串,这样 在spring 解析xml时,遇到 security的标签后,会将这个节点交给 security下的类来执行SecurityNamespaceHandler。

   这样就能保证security初始化的正常。包括生成默认的 FilterChainProxy 和添加一些依赖。生成 AuthenticationManager及其依赖的 ProviderManager 等。

  下次,继续看 url资源控制部分


这一阵子看到了security,很感兴趣。于是研究一下,我在javaeye上查了好多相关的文档,收益匪浅,从入门级的配置问题,到源码级的解读都非常不错,但是还要自己在亲自走一遍流程才踏实。

 

我看的security 3.0的源码,原因是 security 2.0 的源码没办法通过maven获取到 。

 

首先 security的控制内容有: url,method,session三种,我项目中用到的只有 url。下面就按url的流程来走。

思路:  使用filter,过滤所有的url 如 /* 这样,并且这个filter应在最前面,道理就不到说了吧。

     1》 security使用的 filter是 org.springframework.web.filter.DelegatingFilterProxy类,在spring-web jar中。

        @Override

Java代码    收藏代码
  1. protected void initFilterBean() throws ServletException {  
  2.     // If no target bean name specified, use filter name.  
  3.     <span style="color: #ff0000;">if (this.targetBeanName == null) {  
  4.         this.targetBeanName = getFilterName();  
  5.     }</span>  
  6.   
  7.     // Fetch Spring root application context and initialize the delegate early,  
  8.     // if possible. If the root application context will be started after this  
  9.     // filter proxy, we'll have to resort to lazy initialization.  
  10.     synchronized (this.delegateMonitor) {  
  11.         WebApplicationContext wac = findWebApplicationContext();  
  12.         if (wac != null) {  
  13.             this.delegate = initDelegate(wac);  
  14.         }  
  15.     }  
  16. }  
  17.   
  18. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)  
  19.         throws ServletException, IOException {  
  20.   
  21.     // Lazily initialize the delegate if necessary.  
  22.     Filter delegateToUse = null;  
  23.     synchronized (this.delegateMonitor) {  
  24.         if (this.delegate == null) {  
  25.             WebApplicationContext wac = findWebApplicationContext();  
  26.             if (wac == null) {  
  27.                 throw new IllegalStateException("No WebApplicationContext found:  
  28.                                                no ContextLoaderListener registered?");  
  29.             }  
  30.             this.delegate = initDelegate(wac);  
  31.                                     // 该方法中的 代码  
  32.                                    //   Filter delegate =<span style="color: #ff0000;"> wac.getBean(getTargetBeanName(), Filter.class);</span>  
  33.         }  
  34.         delegateToUse = this.delegate;  
  35.     }  
  36.   
  37.     // Let the delegate perform the actual doFilter operation.  
  38.     invokeDelegate(delegateToUse, request, response, filterChain);  
  39. }  

 

  这里需要注意一点 filter-name 必须为 springSecurityFilterChain,从DelegatingFilterProxy这个名字中可以猜到这只是个代理类(确实如此),当这个类执行时会去取得真正的filter类,这个类在spring容器中默认生成id为 springSecurityFilterChain,在3.0中 该filter 添加了一个 targetName 字段,可以从上面红色代码部分看到它的作用,因此可以通过指定targetName字段,来防止和项目中的其他filter冲突。

 

   接下来 该真正的 filterChain出场了,这个类是security事务相关的,应该在security包中。

于是 在 spring-security-web jar中发现了这个类:org.springframeword.security.web.FilterChainProxy ,进去看看可以看出这个就是我要找的类。(关于这一点我是从命名上看出来的,bean的id要和类名保持一致)。

 

 下班了 ,回去再继续添加。

==========================华丽丽的分界线====================================

 

吃完饭 ,继续。

FilterChainProxy 代码:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

     throws IOException, ServletException {

 

        FilterInvocation fi = new FilterInvocation(request, response, chain);

        List<Filter> filters = getFilters(fi.getRequestUrl());

                            // 根据url 得到需要经过的filters

                            // 这里不是很明白,有知道的同学可以留言。

 

        if (filters == null || filters.size() == 0) { // 如果没有合适的 ,就继续进行filter

            if (logger.isDebugEnabled()) {

                logger.debug(fi.getRequestUrl() +

                        filters == null ? " has no matching filters" : " has an empty filter list");

            }

 

            chain.doFilter(request, response);

 

            return;

        }

        //如果有filter 就进行虚拟的filter链。这里并没有跳出容器的 filter链,

        // 当这个虚拟的filter链完成之后,就继续进行 容器的filter

        VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);

        virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());

    }

 接下来就该进行 filterChain了,在security中有好多的filter:

  CHANNEL_FILTER ChannelProcessingFilter 

CONCURRENT_SESSION_FILTER ConcurrentSessionFilter 

SESSION_CONTEXT_INTEGRATION_FILTER HttpSessionContextIntegrationFilter 

LOGOUT_FILTER LogoutFilter 

X509_FILTER X509PreAuthenticatedProcessigFilter 

PRE_AUTH_FILTER Subclass of AstractPreAuthenticatedProcessingFilter 

CAS_PROCESSING_FILTER CasProcessingFilter 

AUTHENTICATION_PROCESSING_FILTER AuthenticationProcessingFilter 

BASIC_PROCESSING_FILTER BasicProcessingFilter 

SERVLET_API_SUPPORT_FILTER classname 

REMEMBER_ME_FILTER RememberMeProcessingFilter 

ANONYMOUS_FILTER AnonymousProcessingFilter 

EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter 

NTLM_FILTER NtlmProcessingFilter 

FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor 

SWITCH_USER_FILTER SwitchUserProcessingFilter 。

 

下面我只分析了 AuthenticationProcessingFilter,这是登录认证处理filter

 public UsernamePasswordAuthenticationFilter() {

        super("/j_spring_security_check");// 这就是 登录验证的 url。

    }

 

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        if (postOnly && !request.getMethod().equals("POST")) {

            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());

        }  // 只允许以post方法 进行认证,能防止一些简单的破解

 

        String username = obtainUsername(request);

        String password = obtainPassword(request);

 

        if (username == null) {

            username = "";

        }

 

        if (password == null) {

            password = "";

        }

 

        username = username.trim();

        // 只是将 username和password封装进去

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

        // 取得AuthenticationManager 进行认证

        return this.getAuthenticationManager().authenticate(authRequest);

    }

 

    从request中取得 username,password,封装进 UsernamePasswordAuthenticationToken 中,

    然后将username中写到 session中,这里对username去掉了首尾的空格

    然后调用 AuthenticationManager的 authenticate方法进行具体的认证操作。

 

     public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {

        Class<? extends Authentication> toTest = authentication.getClass();

        AuthenticationException lastException = null;

        Authentication result = null;

 

        for (AuthenticationProvider provider : getProviders()) {

 

            if (!provider.supports(toTest)) {

                continue;

            }

 

            logger.debug("Authentication attempt using " + provider.getClass().getName());

 

            try {

                result = provider.authenticate(authentication);

 

                if (result != null) {

                    copyDetails(authentication, result);

                    break;

                }

            } catch (AccountStatusException e) {

                // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status

                eventPublisher.publishAuthenticationFailure(e, authentication);

                throw e;

            } catch (AuthenticationException e) {

                lastException = e;

            }

        }

 

        if (result == null && parent != null) {

            // Allow the parent to try.

            try {

                result = parent.authenticate(authentication);

            } catch (ProviderNotFoundException e) {

                // ignore as we will throw below if no other exception occurred prior to calling parent and the parent

                // may throw ProviderNotFound even though a provider in the child already handled the request

            } catch (AuthenticationException e) {

                lastException = e;

            }

        }

 

        if (result != null) {

            if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {

                // Authentication is complete. Remove credentials and other secret data from authentication

                ((CredentialsContainer)result).eraseCredentials();

            }

 

            eventPublisher.publishAuthenticationSuccess(result);

            return result;

        }

 

        // Parent was null, or didn't authenticate (or throw an exception).

 

        if (lastException == null) {

            lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",

                        new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));

        }

 

        eventPublisher.publishAuthenticationFailure(lastException, authentication);

 

        throw lastException;

    }

 

     这里对 用户进行认证,成功就发布成功事件,并返回。 失败就发布失败事件,并返回exception

    这里具体的认证过程还是不大熟悉,等再详细的看明白了 再细说。

     看明白了,authenticationManager可以有多个 provider如 默认的daoAuthenticationProvider 和  JaasAuth ,RememmberMeAuth 等

     下面说 daoAuthenticationProvider 中可以有  UserDetailsService ,

 

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)

            throws AuthenticationException {

        UserDetails loadedUser;

 

        try {

            loadedUser = this.getUserDetailsService().loadUserByUsername(username);

        }

        catch (DataAccessException repositoryProblem) {

            throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);

        }

 

        if (loadedUser == null) {

            throw new AuthenticationServiceException(

                    "UserDetailsService returned null, which is an interface contract violation");

        }

        return loadedUser;

    }

又通过 userDetailsService.loadUserByUsername()  ,当不存在时 ,返回null

    来得到 UserDetails 这样就能把 整个认证过程理顺了

 

   总结一下,  security 的认证过程能理顺了,对其衔接的过渡代码 还有些拿不准,还有 取得filters的 规则还不是很清楚,还要继续看下去。

   已经能理顺了, 在BeanId 类中发现了那些默认的字符串,这样 在spring 解析xml时,遇到 security的标签后,会将这个节点交给 security下的类来执行SecurityNamespaceHandler。

   这样就能保证security初始化的正常。包括生成默认的 FilterChainProxy 和添加一些依赖。生成 AuthenticationManager及其依赖的 ProviderManager 等。

  下次,继续看 url资源控制部分


猜你喜欢

转载自rongdmmap-126-com.iteye.com/blog/1410407