Spring Security Filter详解

来源:互联网 发布:淘宝怎么不能批量发货 编辑:程序博客网 时间:2024/06/05 18:52

0. 概述

经过基于注解的Spring Security原理解析分析,Spring Security本身所做的事情就是在Spring容器中注册了一系列的Filter,这些Filters在检测到满足条件的URL请求时,会执行其定义的处理过程; Security本身默认提供了一些Filter来完成其各种功能; 本文主要分析以下问题:

  1. 默认Filter的作用及配置
  2. 默认Filter的配置及生效示例分析

由于水平受限,如有分析不正确处敬指出。

1. 默认Filter分析

1.1 概述

Security默认的Filter入口在HttpSecurity对象中;关于该对象的加载过程,请参考基于注解的Spring Security原理解析;

在HttpSecurity对象中,实际提供的是各默认Filter的配置类,通过配置类来控制对应Filter的各个属性配置;在配置完成将Filter加载到HttpSecurity中的FilterChain中去。

在HttpSecurity中提供了以下默认Filter及其配置类: 

Configurer Filter 功能说明 OpenIDLoginConfigurer OpenIDAuthenticationFilter 处理OpenID授权请求 HeaderWriterFilter HeadersConfigurer 在返回报文头中添加Security相关信息 CorsConfigurer CorsFilter 提供跨域访问配置支持的Filter SessionManagementConfigurer SessionManagementFilter 会话管理Filter PortMapperConfigurer 无 用于在Http及Https请求之间重定向时的端口判定 JeeConfigurer J2eePreAuthenticatedProcessingFilter 添加J2EE预授权处理机制支持 X509Configurer X509AuthenticationFilter 添加X509预授权处理机制支持 RememberMeConfigurer RememberMeAuthenticationFilter 记住用户名及密码功能支持 ExpressionUrlAuthorizationConfigurer FilterSecurityInterceptor Security的主要Filter,通过调用权限管理器等进行Http访问的权限判断 RequestCacheConfigurer RequestCacheAwareFilter 缓存请求并在必要的时候使用缓存的请求 ExceptionHandlingConfigurer ExceptionTranslationFilter 处理AccessDeniedException及AuthenticationException异常 SecurityContextConfigurer SecurityContextPersistenceFilter SecurityContext对象持久化Filter,用于在请求开始阶段初始化并持久化该对象,在后续的Filter中可以使用该对象来获取信息 ServletApiConfigurer SecurityContextHolderAwareRequestFilter 在原始请求基础上包装一些方法供后续调用 CsrfConfigurer CsrfFilter 跨站请求伪造保护Filter; LogoutConfigurer LogoutFilter 退出登录请求处理Filter AnonymousConfigurer AnonymousAuthenticationFilter 匿名请求控制Filter FormLoginConfigurer UsernamePasswordAuthenticationFilter 表单登录请求处理Filter OAuth2LoginConfigurer OAuth2AuthorizationRequestRedirectFilter OAuth2请求权限控制处理Filter,为其它网站提供本网站Oauth2方式登录,即其它网站通过本网站的账户密码进行登录授权 ChannelSecurityConfigurer ChannelProcessingFilter 通道选择Filter,确保请求是通过正确的通道过来的,如Http或者Https HttpBasicConfigurer BasicAuthenticationFilter Security基础登录授权Filter,将其结果保存在SecurityContextHolder中

默认的Filter并不是在HttpSecurity对象初始化的时候就全部加载,而是根据用户定制情况进行加载,具体加载情况见后文;

1.2 默认Filter默认配置

在WebSecurityConfigurerAdapter类中,存在默认的configure方法,它会提供一些默认的权限控制配置,默认方法实现哪下:

    protected void configure(HttpSecurity http) throws Exception {        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");        http            .authorizeRequests()                .anyRequest().authenticated()                .and()            .formLogin().and()            .httpBasic();    }

同时,在初始化HttpSecurity的方法init中,也会提供一些默认的配置:

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

通过分析这两个配置处,Security默认加载的Filter清单如下: 

Configurer Filter CsrfConfigurer CsrfFilter 空 WebAsyncManagerIntegrationFilter ExceptionHandlingConfigurer ExceptionTranslationFilter HeadersConfigurer HeaderWriterFilter SessionManagementConfigurer SessionManagementFilter SecurityContextConfigurer SecurityContextPersistenceFilter RequestCacheConfigurer RequestCacheAwareFilter AnonymousConfigurer AnonymousAuthenticationFilter ServletApiConfigurer SecurityContextHolderAwareRequestFilter DefaultLoginPageConfigurer DefaultLoginPageGeneratingFilter LogoutConfigurer LogoutFilter FormLoginConfigurer UsernamePasswordAuthenticationFilter HttpBasicConfigurer BasicAuthenticationFilter

1.3 默认Filter用户自定义加载

当开发的时候使用@EnableWebSecurity注解加载Security的配置类时,如果该类继承了WebSecurityConfigurerAdapter类,则可以覆盖其configure方法来配置权限控制参数:

@EnableWebSecurity(debug = true)public class SercurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private TestAuthenticationProvider authenticationProvider;    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .authorizeRequests()                .antMatchers("/*", "/home", "/manager/dbManager.html", "/js/*", "/db/*", "/js/**/*").permitAll()                .anyRequest().authenticated()                .and()                .formLogin()                .loginPage("/login")                .permitAll()                .and()                .logout()                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))                .logoutSuccessUrl("/")                .permitAll()                .and()                .sessionManagement().invalidSessionUrl("/timeout");        http.headers().frameOptions().sameOrigin().httpStrictTransportSecurity().disable();    }    @Autowired    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {        auth.authenticationProvider(authenticationProvider);    }    @Bean    public UserDetailsService getUserDetailservice() {        return new TestUserDetailService();    }}

此时即可配置登录链接、退出登录链接、各个URL的访问权限配置等。

1.4 Filter执行顺序分析

Security在初始化的时候会初始化一系列的Filter,这些Filter之间实际上是有先后关系的,其先后关系是如何控制的?
通过前面的分析,Security的Filters是加载到HttpSecurity对象的Filters属性中去的,在HttpSecurity对象Build的时候生成FilterChain对象,此时会将所有添加的Filters添加到FilterChain对象中并返回。其PerformBuild方法实现如下: 

    @Override    protected DefaultSecurityFilterChain performBuild() throws Exception {        Collections.sort(filters, comparator);        return new DefaultSecurityFilterChain(requestMatcher, filters);    }

可以看到在PerformBuild方法中,第一步是对所有的Fiilters进行排序;其排序规则使用HttpSecurity的属性comparator;我们看下comparator的初始化:

private FilterComparator comparator = new FilterComparator();

再分析下该Comparator的实现:

final class FilterComparator implements Comparator<Filter>, Serializable {    private static final int STEP = 100;    private Map<String, Integer> filterToOrder = new HashMap<String, Integer>();    FilterComparator() {        int order = 100;        put(ChannelProcessingFilter.class, order);        order += STEP;        put(ConcurrentSessionFilter.class, order);        order += STEP;        put(WebAsyncManagerIntegrationFilter.class, order);        order += STEP;        put(SecurityContextPersistenceFilter.class, order);        order += STEP;        put(HeaderWriterFilter.class, order);        order += STEP;        put(CorsFilter.class, order);        order += STEP;        put(CsrfFilter.class, order);        order += STEP;        put(LogoutFilter.class, order);        order += STEP;        put(X509AuthenticationFilter.class, order);        order += STEP;        put(AbstractPreAuthenticatedProcessingFilter.class, order);        order += STEP;        filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",                order);        order += STEP;        put(UsernamePasswordAuthenticationFilter.class, order);        order += STEP;        put(ConcurrentSessionFilter.class, order);        order += STEP;        filterToOrder.put(                "org.springframework.security.openid.OpenIDAuthenticationFilter", order);        order += STEP;        put(DefaultLoginPageGeneratingFilter.class, order);        order += STEP;        put(ConcurrentSessionFilter.class, order);        order += STEP;        put(DigestAuthenticationFilter.class, order);        order += STEP;        put(BasicAuthenticationFilter.class, order);        order += STEP;        put(RequestCacheAwareFilter.class, order);        order += STEP;        put(SecurityContextHolderAwareRequestFilter.class, order);        order += STEP;        put(JaasApiIntegrationFilter.class, order);        order += STEP;        put(RememberMeAuthenticationFilter.class, order);        order += STEP;        put(AnonymousAuthenticationFilter.class, order);        order += STEP;        put(SessionManagementFilter.class, order);        order += STEP;        put(ExceptionTranslationFilter.class, order);        order += STEP;        put(FilterSecurityInterceptor.class, order);        order += STEP;        put(SwitchUserFilter.class, order);    }

很明显执行顺序是在这个地方控制的。

2. 默认Filter配置分析举例

2.1 FormLoginConfigurer

2.1.1 概述

表单登录Filter配置类;用于基于表单的权限控制配置;所有配置项均存在默认值,因此所有参数均可使用默认值; 需要特别说明的是,如果没有指定loginPage配置项,Security将会提供一个默认的登录页面。 该配置项配置的实际上是UsernamePasswordAuthenticationFilter这个Filter。

2.1.2 配置详解

如要配置FormLoginConfigurer,在继承自WebSecurityConfigurerAdapter的类的Configure方法中进行:

http.formLogin()    .loginPage("/login")    .permitAll();

该配置类所包含的配置项清单及其作用见下表:

配置项 配置项说明 loginPage 登录页面,如果用户未指定,Security将提供默认的登录页面; 默认登录页面请求链接:/login usernameParameter 用户名属性的名称;默认是username passwordParameter 密码属性名称,默认是password failureForwardUrl 授权失败时的跳转链接 failureUrl 登录失败时的跳转链接,默认是/login?error failureHandler 登录失败后的处理器 successForwardUrl 授权成功时的跳转链接 successHandler 登录成功时的处理器 defaultSuccessUrl 指定如果用户登录前未访问需要授权访问的页面,登录成功后的跳转链接 loginProcessingUrl 登录请求处理链接 authenticationDetailsSource 保存登录请求地址及其SessionID的对象

2.1.3 Filter配置过程分析

上方已经提到过,Configurer是用来配置Filter的各个属性的;实际上最终添加到HttpSecurity对象的Filters属性中去的还是各个Filter;那Configurer与Filter分别是如何发挥作用的?
在此我们将以FormLoginConfigurer为例来分析其生效过程。

先来看下配置加载过程:
FilterConfigurer加载过程

其中HttpSecurity继承自AbstractConfiguredSecurityBuilder类,其配置入口是其方法formLogin;实际最终调用的是其父类的add方法,将FormLoginConfigurer对象添加进去。
add方法源码如下:

private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {        Assert.notNull(configurer, "configurer cannot be null");        Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer                .getClass();        synchronized (configurers) {            if (buildState.isConfigured()) {                throw new IllegalStateException("Cannot apply " + configurer                        + " to already built object");            }            List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers                    .get(clazz) : null;            if (configs == null) {                configs = new ArrayList<SecurityConfigurer<O, B>>(1);            }            configs.add(configurer);            this.configurers.put(clazz, configs);            if (buildState.isInitializing()) {                this.configurersAddedInInitializing.add(configurer);            }        }    }

该段代码实际所做的事情就是: 将FormLoginConfigurer对象添加到HttpSecurity的configurers属性中去;

在基于注解的Spring Security原理解析一文中已经说明,HttpSecurity最终会被加载到WebSecurity的securityFilterChainBuilders属性中去; 添加进去后,在WebSecurity对象的build方法中,会调用HttpSecurity的build方法生成FilterChain对象。
HttpSecurity对象的其Build方法实现如下(AbstractConfiguredSecurityBuilder父类中实现):

@Override    protected final O doBuild() throws Exception {        synchronized (configurers) {            buildState = BuildState.INITIALIZING;            beforeInit();            init();            buildState = BuildState.CONFIGURING;            beforeConfigure();            configure();            buildState = BuildState.BUILDING;            O result = performBuild();            buildState = BuildState.BUILT;            return result;        }    }

可以看到在performBuild前会调用configure方法; 跟踪HttpSecurity的该方法,其实现如下: 

    private void configure() throws Exception {        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();        for (SecurityConfigurer<O, B> configurer : configurers) {            configurer.configure((B) this);        }    }

可以看到实际调用的是每个FilterConfigurer的configure方法,此处也就是FormLoginConfigurer的configure方法(实现在其父类中):

@Override    public void configure(B http) throws Exception {        PortMapper portMapper = http.getSharedObject(PortMapper.class);        if (portMapper != null) {            authenticationEntryPoint.setPortMapper(portMapper);        }        authFilter.setAuthenticationManager(http                .getSharedObject(AuthenticationManager.class));        authFilter.setAuthenticationSuccessHandler(successHandler);        authFilter.setAuthenticationFailureHandler(failureHandler);        if (authenticationDetailsSource != null) {            authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);        }        SessionAuthenticationStrategy sessionAuthenticationStrategy = http                .getSharedObject(SessionAuthenticationStrategy.class);        if (sessionAuthenticationStrategy != null) {            authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);        }        RememberMeServices rememberMeServices = http                .getSharedObject(RememberMeServices.class);        if (rememberMeServices != null) {            authFilter.setRememberMeServices(rememberMeServices);        }        F filter = postProcess(authFilter);        http.addFilter(filter);    }

该方法是重点了,可以看到执行过程实际就是根据Configurer对象中的属性更新authFilter对象的属性,最后调用HttpSecurity的addFilter方法将生成的Filter添加到了HttpSecurity对象中去。authFilter对象实现类型为: DefaultLoginPageGeneratingFilter。

在Configure方法完成后,HttpSecurity的build方法最终调用的是其performBuild方法,该方法实现如下: 

    @Override    protected DefaultSecurityFilterChain performBuild() throws Exception {        Collections.sort(filters, comparator);        return new DefaultSecurityFilterChain(requestMatcher, filters);    }

实际上就是组装了一个包含已配置的Filter的一个FilterChain对象。此时
至此,Filter的配置加载过程已经分析完成。

2.1.4 Filter执行过程分析

经过2.1.3中分析,FormLoginConfigurer最终是向HttpSecurity中添加了DefaultLoginPageGeneratingFilter这样一个Filter。其doFilter方法如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        boolean loginError = isErrorPage(request);        boolean logoutSuccess = isLogoutSuccess(request);        if (isLoginUrlRequest(request) || loginError || logoutSuccess) {            String loginPageHtml = generateLoginPageHtml(request, loginError,                    logoutSuccess);            response.setContentType("text/html;charset=UTF-8");            response.setContentLength(loginPageHtml.length());            response.getWriter().write(loginPageHtml);            return;        }        chain.doFilter(request, response);    }

实际也就是将判断是否是登录、登录失败、退出登录请求,如果是的话则跳转到登录页面;此时将不会再执行后续的Filters了。如果不是上述请求,则继续执行FilterChain中的其它Filters。

参考资料:

  1. http://www.mossle.com/docs/auth/html/ch101-filters.html
原创粉丝点击