spring security源碼分析心得


看了半天的文檔及源碼,終於理出了spring-security的一些總體思路,spring security主要分認證(authentication)和授權(authority)。

1.認證authentication

認證主要代碼在spring-security-core下的包org.springframework.security.authentication下,主類:AuthenticationManager、AuthenticationProvider

其關系如下:

 

 2.授權Authorization

授權也稱Access Control,主要代碼在spring-security-core下的包org.springframework.security.access下,主類:AccessDecisionManager、SecurityMetadataSource。它們的關系通過ConfigAttribute關聯起來。

SecurityMetadataSource獲取ConfigAttribute,方法:

Collection<ConfigAttribute> getAttributes(Object object)throws IllegalArgumentException;

AccessDecisionManager根據進行授權,方法:

void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,InsufficientAuthenticationException;

其實現類:AffirmativeBased的授權邏輯如下:

/**
     * This concrete implementation simply polls all configured
     * {@link AccessDecisionVoter}s and grants access if any
     * <code>AccessDecisionVoter</code> voted affirmatively. Denies access only if there
     * was a deny vote AND no affirmative votes.
     * <p>
     * If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
     * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
     * false).
     * </p>
     *
     * @param authentication the caller invoking the method
     * @param object the secured object
     * @param configAttributes the configuration attributes associated with the method
     * being invoked
     *
     * @throws AccessDeniedException if access is denied
     */
    public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;

        for (AccessDecisionVoter voter : getDecisionVoters()) {
            int result = voter.vote(authentication, object, configAttributes);

            if (logger.isDebugEnabled()) {
                logger.debug("Voter: " + voter + ", returned: " + result);
            }

            switch (result) {
            case AccessDecisionVoter.ACCESS_GRANTED:
                return;

            case AccessDecisionVoter.ACCESS_DENIED:
                deny++;

                break;

            default:
                break;
            }
        }

        if (deny > 0) {
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }

        // To get this far, every AccessDecisionVoter abstained
        checkAllowIfAllAbstainDecisions();
    }

從上文可以看出,真正的授權是通過AccessDecisionVoter來完成的。

3.認證和授權的集成AbstractSecurityInterceptor

AbstractSecurityInterceptor包含了四個instance及其get/set方法

    private AccessDecisionManager accessDecisionManager;
    private AfterInvocationManager afterInvocationManager;
    private AuthenticationManager authenticationManager = new NoOpAuthenticationManager();
    private RunAsManager runAsManager = new NullRunAsManager();

加一個抽象的方法:

    /**
     * Indicates the type of secure objects the subclass will be presenting to the
     * abstract parent for processing. This is used to ensure collaborators wired to the
     * {@code AbstractSecurityInterceptor} all support the indicated secure object class.
     *
     * @return the type of secure object the subclass provides services for
     */
    public abstract Class<?> getSecureObjectClass();

AbstractSecurityInterceptor的實現類有兩個:

3.1 FilterSecurityInterceptor

定義:

/**
 * Performs security handling of HTTP resources via a filter implementation.
 * <p>
 * The <code>SecurityMetadataSource</code> required by this security interceptor is of
 * type {@link FilterInvocationSecurityMetadataSource}.
 * <p>
 * Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
 * </p>
 *
 * @author Ben Alex
 * @author Rob Winch
 */
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {}

實現了標准的servlet的Filter接口,其邏輯如下:

    /**
     * Method that is actually called by the filter chain. Simply delegates to the
     * {@link #invoke(FilterInvocation)} method.
     *
     * @param request the servlet request
     * @param response the servlet response
     * @param chain the filter chain
     *
     * @throws IOException if the filter chain fails
     * @throws ServletException if the filter chain fails
     */
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

最重要的實現invoke

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if ((fi.getRequest() != null)
                && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            // filter already applied to this request and user wants us to observe
            // once-per-request handling, so don't re-do security checking
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            // first time this request being called, so perform security checking
            if (fi.getRequest() != null) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }

            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, null);
        }
    }

3.2 MethodSecurityInterceptor

定義:

/**
 * Provides security interception of AOP Alliance based method invocations.
 * <p>
 * The <code>SecurityMetadataSource</code> required by this security interceptor is of
 * type {@link MethodSecurityMetadataSource}. This is shared with the AspectJ based
 * security interceptor (<code>AspectJSecurityInterceptor</code>), since both work with
 * Java <code>Method</code>s.
 * <p>
 * Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
 *
 * @author Ben Alex
 * @author Rob Winch
 */
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements
        MethodInterceptor {}

其invoke方法如下:

    /**
     * This method should be used to enforce security on a <code>MethodInvocation</code>.
     *
     * @param mi The method being invoked which requires a security decision
     *
     * @return The returned value from the method invocation (possibly modified by the
     * {@code AfterInvocationManager}).
     *
     * @throws Throwable if any error occurs
     */
    public Object invoke(MethodInvocation mi) throws Throwable {
        InterceptorStatusToken token = super.beforeInvocation(mi);

        Object result;
        try {
            result = mi.proceed();
        }
        finally {
            super.finallyInvocation(token);
        }
        return super.afterInvocation(token, result);
    }

 4.Spring Security Java Config ---@EnableWebSecurity

 將@EnableWebSecurity注解加到@Configuration下來獲得spring securiy的安全性。

WebSecurityConfigurer定義的配置或者對WebSecurityConfigurerAdapter類的擴展類示例如下:

  @Configuration
  @EnableWebSecurity
  public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
      @Override
      public void configure(WebSecurity web) throws Exception {
          web.ignoring()
          // Spring Security should completely ignore URLs starting with /resources/
                  .antMatchers(&quot;/resources/&quot;);
      }
 
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests().antMatchers(&quot;/public/&quot;).permitAll().anyRequest()
                  .hasRole(&quot;USER&quot;).and()
                  // Possibly more configuration ...
                  .formLogin() // enable form based log in
                  // set permitAll for all URLs associated with Form Login
                  .permitAll();
      }
 
      @Override
      protected void configure(AuthenticationManagerBuilder auth) {
          auth
          // enable in memory based authentication with a user named &quot;user&quot; and &quot;admin&quot;
          .inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;)
                  .and().withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
      }
 
      // Possibly more overridden methods ...
  }

4.1 WebSecurityConfigurer

 WebSecurityConfigurer允許對WebSecurity進行定制化,在絕大部分情景下,開發者使用@EnableWebSecurity注解或者對WebSecurityConfigurerAdapter進行重寫的方式來自動應用@EnableWebSecurity注解。定義如下:

public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
        SecurityConfigurer<Filter, T> {

}

4.2 WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter提供了創建WebSecurityConfigurer實例的便利方法,它是一個基類。該類的實現通過重寫方法來實現定制化。

它自動從SpringFactoriesLoader查找AbstractHttpConfigurer,從而讓開發者可以擴展。為達到這個目的,必須創建一個AbstractHttpConfigurer的擴展類,然后在classpath路徑下創建一個文件META-INF/spring.factories,示例如下:

org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer

如果你有多個擴展類,可以使用逗號分隔,示例如下:

 org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer, sample.OtherThatExtendsAbstractHttpConfigurer

 4.2.1 初始化

 初始化分兩個過程:獲取HttpSecurity,配置FilterSecurityInterceptor到WebSecurity

    public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
    }

獲取HttpSecurity的過程:

/**
     * Creates the {@link HttpSecurity} or returns the current instance
     *
     * ] * @return the {@link HttpSecurity}
     * @throws Exception
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected final HttpSecurity getHttp() throws Exception {
        if (http != null) {
            return http;
        }

        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                .postProcess(new DefaultAuthenticationEventPublisher());
        localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

        AuthenticationManager authenticationManager = authenticationManager();
        authenticationBuilder.parentAuthenticationManager(authenticationManager);
        Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);
        if (!disableDefaults) {
            // @formatter:off
            http
                .csrf().and()
                .addFilter(new WebAsyncManagerIntegrationFilter())
                .exceptionHandling().and()
                .headers().and()
                .sessionManagement().and()
                .securityContext().and()
                .requestCache().and()
                .anonymous().and()
                .servletApi().and()
                .apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
                .logout();
            // @formatter:on
            ClassLoader classLoader = this.context.getClassLoader();
 List<AbstractHttpConfigurer> defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

            for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }
        configure(http);
        return http;
    }

 5. xml配置解析類SecurityNamespaceHandler

它的解析器有以下幾種:

    private void loadParsers() {
        // Parsers
        parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
        parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
        parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
        parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
        parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
        parsers.put(Elements.AUTHENTICATION_PROVIDER,
                new AuthenticationProviderBeanDefinitionParser());
        parsers.put(Elements.GLOBAL_METHOD_SECURITY,
                new GlobalMethodSecurityBeanDefinitionParser());
        parsers.put(Elements.AUTHENTICATION_MANAGER,
                new AuthenticationManagerBeanDefinitionParser());
        parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE,
                new MethodSecurityMetadataSourceBeanDefinitionParser());

        // Only load the web-namespace parsers if the web classes are available
        if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass()
                .getClassLoader())) {
            parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
            parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
            parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
            parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE,
                    new FilterInvocationSecurityMetadataSourceParser());
            parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
            filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
        }

        if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
            parsers.put(Elements.WEBSOCKET_MESSAGE_BROKER,
                    new WebSocketMessageBrokerSecurityBeanDefinitionParser());
        }
    }

6.小結

spring支持注解和xml配置兩種方式,因此分析源碼可以從xml配置及注解兩方面入手,相互印證。

參考文獻:

【1】https://spring.io/guides/topicals/spring-security-architecture/

 【2】http://zzy.cincout.cn/2016/12/23/spring-security-2016-12-23-spring-security-01/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM