基于注解的Spring Security原理解析

来源:互联网 发布:ant java 参数详解 编辑:程序博客网 时间:2024/05/29 06:44

概述:

Spring Security就是引入了一系列的SecurityFilter,将其添加到Spring中去了;在有请求时,根据URL是否符合每个Filter的规则来判断是否需要该Filter来进行处理。
我们先来看下Security的加载过程,在下文将详细说明该加载过程是如何分析出来的:
这里写图片描述

0. 示例:

示例代码地址:(https://github.com/icarusliu/Test)
为使用itellij的工程;

其入口为SecurityConfig类; :

package com.liuqi.tool.todo.common.security; /** * Created by icaru on 2017/7/2. */import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;/** * <p> * </p> * * @Author icaru * @Date 2017/7/2 23:12 * @Version V1.0 * --------------Modify Logs------------------ * @Version V1.* * @Comments <p></p> * @Author icaru * @Date 2017/7/2 23:12 **/@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();    }}

要使Spring Security生效,主要是需要配置@EnableWebSecurity注解;
运行工程中的Application的Main函数即可启动Spring Boot;启动完成后通过浏览器打开http://localhost即可看到首页,可以点击用户管理等后台管理模块会要求进行登录; 使用admin/123456登录即可看到效果。

注意在EnableWebSecurity上指定了Debug为True;此时在启动日志里面可以看到以下日志: 

2017-11-30 16:08:38.371 INFO 4964 — [ restartedMain] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@466f1fc3, org.springframework.security.web.context.SecurityContextPersistenceFilter@75b52145, org.springframework.security.web.header.HeaderWriterFilter@55e7044d, org.springframework.security.web.csrf.CsrfFilter@120c3a8a, org.springframework.security.web.authentication.logout.LogoutFilter@1ad0d698, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@77b3a17c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@58f5459, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6827fdc9, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@386ddda2, org.springframework.security.web.session.SessionManagementFilter@69e9376, org.springframework.security.web.access.ExceptionTranslationFilter@6702f90b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7c7258c6]
2017-11-30 16:08:38.494 INFO 4964 — [ restartedMain] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: Ant [pattern=’/h2-console/**’], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@56985854, org.springframework.security.web.context.SecurityContextPersistenceFilter@11b6e572, org.springframework.security.web.header.HeaderWriterFilter@4df71a65, org.springframework.security.web.authentication.logout.LogoutFilter@7e2ca6e2, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@58174120, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7bad0a32, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@efc0310, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@614ec113, org.springframework.security.web.session.SessionManagementFilter@5ac55cb9, org.springframework.security.web.access.ExceptionTranslationFilter@78924c6a, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@5c8ea323]
2017-11-30 16:08:38.494 WARN 4964 — [ restartedMain] o.s.s.c.a.web.builders.WebSecurity :


** Security debugging is enabled. *****
** This may include sensitive information. *****
** Do not use in a production system! *****

可以看到Security启动时加载的各个Filter;

2 引入:

自定义一个类,在其上添加@EnableWebSecurity注解即可;
有两种方式:
一是直接在入口类中加上该注解,入口类不需要继承自WebSecurityConfigurerAdapter
二是添加注解后再继承自WebSecurityConfigurerAdapter类; 继承自该类后,将会自动添加如表单登录、记住用户名密码等十来个个Filter
这些Filter是在HttpSecurity中定义的; 而HttpSecurity又是在WebSecurityConfigurerAdapter中创建的使用的

为什么添加了EnableWebSecurity注解后Spring Security即启动了呢?
我们看下这个注解的定义:

/* * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.security.config.annotation.web.configuration;import java.lang.annotation.Documented;import java.lang.annotation.Retention;import java.lang.annotation.Target;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;import org.springframework.security.config.annotation.web.WebSecurityConfigurer;@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)@Target(value = { java.lang.annotation.ElementType.TYPE })@Documented@Import({ WebSecurityConfiguration.class,        SpringWebMvcImportSelector.class })@EnableGlobalAuthentication@Configurationpublic @interface EnableWebSecurity {    /**     * Controls debugging support for Spring Security. Default is false.     * @return if true, enables debug support with Spring Security     */    boolean debug() default false;}

其重点就是@Import中将WebSecurityConfiguration类引入; 同时自己也被Configuration所注解;
@Import的含义就在于,它相当于以前基于XML配置方式的一个配置文件,配置在WebSecurityConfiguration中的Bean以及WebSecurityConfiguration对象本身都会被Spring容器所托管;
接下来我们研究下WebSecurityConfiguration类; 来分析下SpringSecurity启动后做了什么。

3 WebSecurityConfiguration

WebSecurityConfiguration对象做了以下事情:一是创建了一个WebSecurity对象;二是通过WebSecurity对象创建了名称为springSecurityFilterChain 的Filter并托管到Spring容器中,在springSecurityFilterChain 这个Filter中,添加到一些默认的Security的Filter;

3.1 创建WebSecurity对象

首先来看WebSecurity对象的创建,在方法setFilterChainProxySecurityConfigurer 中; 其实现如下: 

@Autowired(required = false)    public void setFilterChainProxySecurityConfigurer(            ObjectPostProcessor<Object> objectPostProcessor,            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)            throws Exception {        webSecurity = objectPostProcessor                .postProcess(new WebSecurity(objectPostProcessor));        if (debugEnabled != null) {            webSecurity.debug(debugEnabled);        }        Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);        Integer previousOrder = null;        Object previousConfig = null;        for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {            Integer order = AnnotationAwareOrderComparator.lookupOrder(config);            if (previousOrder != null && previousOrder.equals(order)) {                throw new IllegalStateException(                        "@Order on WebSecurityConfigurers must be unique. Order of "                                + order + " was already used on " + previousConfig + ", so it cannot be used on "                                + config + " too.");            }            previousOrder = order;            previousConfig = config;        }        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {            webSecurity.apply(webSecurityConfigurer);        }        this.webSecurityConfigurers = webSecurityConfigurers;    }

主要做了两个事情,一是通过New的方式创建了个WebSecurity对象;二是将一些默认的SecurityConfigurer对象从Spring容器中获取到并添加到WebSecurity对象中;
注意其中webSecurityConfigurers 的值,是通过@Value注入的方法获取的值,其方法如下所示:

    @SuppressWarnings({ "rawtypes", "unchecked" })    public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {        List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();        Map<String, WebSecurityConfigurer> beansOfType = beanFactory                .getBeansOfType(WebSecurityConfigurer.class);        for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {            webSecurityConfigurers.add(entry.getValue());        }        return webSecurityConfigurers;    }

该方法在Spring容器中查找所有的类型为WebSecurityConfigurer的Bean,将其添加到List中并返回;
我们再回过头来看WebSecurityConfigurerAdapter类,可以看到其实现WebSecurityConfigurer类;因此,adapter就在这个地方被加载到webSecurityConfigurers中去了; 最终被添加到WebSecurity对象中去了。

3.2 创建名称为:springSecurityFilterChain 的Filter并托管到Spring容器;

该步骤通过WebSecurityConfiguration类的 setFilterChainProxySecurityConfigurer 方法执行: 

    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)    public Filter springSecurityFilterChain() throws Exception {        boolean hasConfigurers = webSecurityConfigurers != null                && !webSecurityConfigurers.isEmpty();        if (!hasConfigurers) {            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor                    .postProcess(new WebSecurityConfigurerAdapter() {                    });            webSecurity.apply(adapter);        }        return webSecurity.build();    }

这个方法就是通过WebSecurity的Build方法,生成了一个Filter并注入到Spring容器中;
完成这个步骤后,当SpringMVC获取到URL请求时,就会调用该Filter来看是否需要交由该Filter处理该请求; 这部分内容已经不属于Security内容,因此在此暂时不进行研究。

这个里面还有个疑问,如果要保证在执行springSecurityFilterChain 方法时,WebSecurity已经初始化,那setFilterChainProxySecurityConfigurer 方法就必须要在其前面执行。分析这两个方法,第一个为Bean注解,第二个为Autowired注解,两者唯一的区别在于这两个注解不一样,那是否可以理解为Autowired注解会在Bean注解前执行?这个留待验证!

实际上经过以上分析,SpringSecurity的启动过程基本已经分析完成;
接下来我们可以深入分析下WebSecurity对象所做的一些事情,以及Security加载的默认的Filter在哪控制以及如何生效的;

4 WebSecurity分析

WebSecurity对象在WebSecurityConfiguration中初始化后,在生成名称为springSecurityFilterChain 的Filter时,会调用其Build方法;
Build方法实际上为WebSecurity的父类提供的方法,最终调用的为其本身的performBuild 方法: 

@Override    protected Filter performBuild() throws Exception {        Assert.state(                !securityFilterChainBuilders.isEmpty(),                "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "                        + WebSecurity.class.getSimpleName()                        + ".addSecurityFilterChainBuilder directly");        int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();        List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(                chainSize);        for (RequestMatcher ignoredRequest : ignoredRequests) {            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));        }        for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {            securityFilterChains.add(securityFilterChainBuilder.build());        }        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);        if (httpFirewall != null) {            filterChainProxy.setFirewall(httpFirewall);        }        filterChainProxy.afterPropertiesSet();        Filter result = filterChainProxy;        if (debugEnabled) {            logger.warn("\n\n"                    + "********************************************************************\n"                    + "**********        Security debugging is enabled.       *************\n"                    + "**********    This may include sensitive information.  *************\n"                    + "**********      Do not use in a production system!     *************\n"                    + "********************************************************************\n\n");            result = new DebugFilter(filterChainProxy);        }        postBuildAction.run();        return result;    }

该方法实际返回的对象为FilterChainProxy对象; 添加的为securityFilterChainBuilders中的对象Build所产生的Filters;
在以下代码:

for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {            securityFilterChains.add(securityFilterChainBuilder.build());        }        FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);        if (httpFirewall != null) {            filterChainProxy.setFirewall(httpFirewall);        }        filterChainProxy.afterPropertiesSet();

将securityFilterChainBuilders属性中的对象构建产生的Filter添加到结果中;

其中该属性的初始化是在addSecurityFilterChainBuilder方法中进行的:通过该方法的调用逻辑查找,可以看到它是在WebSecurityConfigurerAdapter的Init方法中调用的:

    public WebSecurity addSecurityFilterChainBuilder(        SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {        this.securityFilterChainBuilders.add(securityFilterChainBuilder);        return this;    }

其具体实现如下:

    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对象添加到WebSecurity的securityFilterChainBuilders属性中去了;
init方法在WebSecurityConfigurerAdapter对象初始化的时候即会执行,也即在WebSecurityConfigurerAdapter对象初始化的时候就会将HttSecurity对象添加到WebSecurity的Builders属性中去;而WebSecurityConfigurerAdapter对象的引入过程在3.1步骤中已详细说明;
最终在WebSecurity对象的performBuild 方法中,将httpSecurity对象Build产生的Filter添加到SecurityFilterChain中去了。

那么HttpSecurity对象Build产生的Filter是什么内容? 下一步将了解这部分内容;

5 HttpSecurity功能

HttpSecurity用于提供一系列的Security默认的Filter,最终在WebSecurity对象中,组装到最终产生的springSecurityFilterChain 对象中去;
其主要包含两个过程:
一是生成默认的FilterConfigurer对象并添加到其filters属性中存储 ;
二是调用其performBuild方法生成DefaultSecurityFilterChain对象;
以LoginForm为列,其添加FilterConfigurer的方法如下: 

    public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {        return getOrApply(new FormLoginConfigurer<HttpSecurity>());    }

这个方法的调用入口在WebSecurityConfigurerAdapter的Configure方法中 ; 如果工程提供的继承该类的类中覆盖了该方法,则会在视情况在这个方法中进行调用。
如示例工程: 

 @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();    }

其performBuild方法如下:

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

实际就组装了一个DefaultSecurityFilterChain对象并返回 ;

下一步探讨在HttpSecurity中添加进去的Filter怎么样生效。

6 WebSecurity对象的performBuild 方法中,返回的是FilterChainProxy对象,其实际包含了一个SecurityFilterChain 的列表对象 ;

先来看FilterChainProxy的关键方法doFilter:

public void doFilter(ServletRequest request, ServletResponse response,            FilterChain chain) throws IOException, ServletException {        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;        if (clearContext) {            try {                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);                doFilterInternal(request, response, chain);            }            finally {                SecurityContextHolder.clearContext();                request.removeAttribute(FILTER_APPLIED);            }        }        else {            doFilterInternal(request, response, chain);        }    }

其实际调用的为doFilterInternal :

private void doFilterInternal(ServletRequest request, ServletResponse response,            FilterChain chain) throws IOException, ServletException {        FirewalledRequest fwRequest = firewall                .getFirewalledRequest((HttpServletRequest) request);        HttpServletResponse fwResponse = firewall                .getFirewalledResponse((HttpServletResponse) response);        List<Filter> filters = getFilters(fwRequest);        if (filters == null || filters.size() == 0) {            if (logger.isDebugEnabled()) {                logger.debug(UrlUtils.buildRequestUrl(fwRequest)                        + (filters == null ? " has no matching filters"                                : " has an empty filter list"));            }            fwRequest.reset();            chain.doFilter(fwRequest, fwResponse);            return;        }        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);        vfc.doFilter(fwRequest, fwResponse);    }

该方法中,第一步为根据请求查找满足条件的Filters,第二步为根据找到的Filters来创建一个VirtualFilterChain对象,最终调用其doFilter方法;
其doFilter方法如下:

@Override        public void doFilter(ServletRequest request, ServletResponse response)                throws IOException, ServletException {            if (currentPosition == size) {                if (logger.isDebugEnabled()) {                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)                            + " reached end of additional filter chain; proceeding with original chain");                }                // Deactivate path stripping as we exit the security filter chain                this.firewalledRequest.reset();                originalChain.doFilter(request, response);            }            else {                currentPosition++;                Filter nextFilter = additionalFilters.get(currentPosition - 1);                if (logger.isDebugEnabled()) {                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)                            + " at position " + currentPosition + " of " + size                            + " in additional filter chain; firing Filter: '"                            + nextFilter.getClass().getSimpleName() + "'");                }                nextFilter.doFilter(request, response, this);            }        }

第一步判断是否additionalFilters中的Filter全部执行完毕,如果没有执行完毕则继续执行其中的Filter;否则执行OriginalChain中的Filters;
这里通过游标的方式判断当前执行的是第几个Filter; 注意最后一步nextFilter.doFilter的时候,将VirtualFilterChain对象传递到了要执行的Filter中; 然后在执行的Filter中决定是否要继续调用FilterChain中的下一个Filter;以LogoutFilter为例,其doFilter方法如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        if (requiresLogout(request, response)) {            Authentication auth = SecurityContextHolder.getContext().getAuthentication();            if (logger.isDebugEnabled()) {                logger.debug("Logging out user '" + auth                        + "' and transferring to logout destination");            }            this.handler.logout(request, response, auth);            logoutSuccessHandler.onLogoutSuccess(request, response, auth);            return;        }        chain.doFilter(request, response);    }

可以看到,先判断是否是退出登录请求,如果是的话则进行登出处理;此时不再执行Chain中的其它Filter; 如果不是才执行Chain中的其它Filter;
由此可见,为什么要使用FilterChain而不是For循环的方式来执行所有Filter,也是为了在Filter中方便的控制下面的Filter是否要继续执行。

阅读全文
0 0