`
kobe学java
  • 浏览: 249942 次
  • 性别: Icon_minigender_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资源控制部分


 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics