zoukankan      html  css  js  c++  java
  • 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/

  • 相关阅读:
    业务领域建模Domain Modeling
    用例建模Use Case Modeling
    分析一套源代码的代码规范和风格并讨论如何改进优化代码
    结合工程实践选题调研分析同类软件产品
    如何提高程序员的键盘使用效率
    python知识准备及环境配置
    一张图片在Python操作下的4种玩法(附源码)
    Python中的错误和异常
    当你忘记网站上的密码时怎么办?Python如何快速帮你找回?
    Python最简单的图片爬虫,20行代码带你爬遍整个网站
  • 原文地址:https://www.cnblogs.com/davidwang456/p/6522925.html
Copyright © 2011-2022 走看看