springboot整合springsecurity从Hello World到源码解析(二):springsecurity配置加载解析

版权声明:本文为博主原创文章(https://jsbintask.cn),未经博主允许不得转载。 https://blog.csdn.net/Iperishing/article/details/86505017

springboot整合springsecurity从Hello World到源码解析(二):springsecurity配置加载解析

cover

上一篇博客我们介绍了hellowrold入门,并且成功的看到了springsecurity的拦截效果,接下来我们就来看看springsecurity是如何做到的。

系列其它文章
[springboot整合springsecurity从Hello World到源码解析(一):hello world程序入门](https://blog.csdn.net/Iperishing/article/details/86504847)
[springboot整合springsecurity从Hello World到源码解析(二):springsecurity配置加载解析](https://blog.csdn.net/Iperishing/article/details/86505017)
[springboot整合springsecurity从Hello World到源码解析(三):基础配置详解](https://blog.csdn.net/Iperishing/article/details/86505120)
[springboot整合springsecurity从Hello World到源码解析(四):springsecurity基础架构解析](https://blog.csdn.net/Iperishing/article/details/86521621)
[springboot整合springsecurity从Hello World到源码解析(五):springsecurity+jwt整合restful服务](https://blog.csdn.net/Iperishing/article/details/86575416)

启动配置详解

我们知道(不知道的就当知道吧,哈哈),springboot启动时会帮我自动配置好很多的默认配置项,并且加载配置类都会写在spring.factories文件中,所以我们这里开始,看看springsecurity做了
那些配置,打开idea,ctrl+shift+n * 2,查找spring.factories文件:如下:

spring.factories

spring.factories


随后在该配置文件中,查找security,如下:
security

security


我们可以看到,一共初始化了9个security相关的类,这里我们不关注oauth2(以后再说)和reactive(springboot2以后新特性),还有
SecurityAutoConfiguration, SecurityRequestMatcherProviderAutoConfiguration, SecurityFilterAutoConfiguration, UserDetailsServiceAutoConfiguration这四个类,首先我们看下

扫描二维码关注公众号,回复: 5020724 查看本文章

SecurityAutoConfiguration:

 1@Configuration
 2@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
 3@EnableConfigurationProperties(SecurityProperties.class)
 4@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
 5        SecurityDataConfiguration.class })
 6public class SecurityAutoConfiguration {
 7
 8    @Bean
 9    @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
10    public DefaultAuthenticationEventPublisher authenticationEventPublisher(
11            ApplicationEventPublisher publisher) {
12        return new DefaultAuthenticationEventPublisher(publisher);
13    }
14
15}
  • 1.可以看出,这个类初始化了DefaultAuthenticationEventPublisher,看名字就知道,一个事件发布器,其内部实现就是spring的ApplicationEventPublisher,
    用于springsecurity各种权限时间的交互,如登陆失败,会发布一个事件,然后通知其它组件做出相应的响应。

  • 2.导入了一个配置类,SecurityProperties,如下:

1private String name = "user";
2
3private String password = UUID.randomUUID().toString();
4
5private List<String> roles = new ArrayList<>();
6
7private boolean passwordGenerated = true;

现在我们知道,我们上一篇博客中yml文件中配置的用户名密码就是这这里的配置,如果不进行配置,默认生成一个uuid的密码,从控制台可以看到该密码。

  • 3.另外导入了三个配置项
    SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class
    其中data相关的因为此处我们没有导入spring-data相关的引用,不生效。
    然后我们继续观察 WebSecurityEnablerConfiguration.class,看名字我们知道这是web环境下的初始化的配置,如下:
1@Configuration
2@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
3@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
4@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
5@EnableWebSecurity
6public class WebSecurityEnablerConfiguration {
7
8}

主要作用帮我们加入了 @EnableWebSecurity注解,该注解的作用为开启springsecurity httpsecurity的自定义配置,即我们可以自己定义web环境的url配置(后面的主要关注点)。
接下来就是@SpringBootWebSecurityConfiguration,如下:

 1@Configuration
 2@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
 3@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
 4@ConditionalOnWebApplication(type = Type.SERVLET)
 5public class SpringBootWebSecurityConfiguration {
 6
 7    @Configuration
 8    @Order(SecurityProperties.BASIC_AUTH_ORDER)
 9    static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
10
11    }
12
13}

关键点来了,这个配置项检查了servlet环境下spring容器中是否有WebSecurityConfiguraerAdapter这个bean,如果没有,就帮我们默认初始化了一个。所以我们对于springsecurity
的配置就要继承WebSecurityConfigurerAdapter,然后实现自定义的配置。
以上就是SecurityAutoConfiguration该配置项的作用,接下来我们看下SecurityRequestMatcherProviderAutoConfiguration

SecurityRequestMatcherProviderAutoConfiguration

 1@Configuration
 2@ConditionalOnClass({ RequestMatcher.class })
 3@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
 4public class SecurityRequestMatcherProviderAutoConfiguration {
 5
 6    @Configuration
 7    @ConditionalOnClass(DispatcherServlet.class)
 8    @ConditionalOnBean(HandlerMappingIntrospector.class)
 9    public static class MvcRequestMatcherConfiguration {
10
11        @Bean
12        @ConditionalOnClass(DispatcherServlet.class)
13        public RequestMatcherProvider requestMatcherProvider(
14                HandlerMappingIntrospector introspector) {
15            return new MvcRequestMatcherProvider(introspector);
16        }
17
18    }
19
20    @Configuration
21    @ConditionalOnClass(ResourceConfig.class)
22    @ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
23    @ConditionalOnBean(JerseyApplicationPath.class)
24    public static class JerseyRequestMatcherConfiguration {
25
26        @Bean
27        public RequestMatcherProvider requestMatcherProvider(
28                JerseyApplicationPath applicationPath) {
29            return new JerseyRequestMatcherProvider(applicationPath);
30        }
31
32    }
33
34}

可以看出,主要初始化了一个MvcRequestMatcherProvider,了解过springmvc的同学应该知道,springmvc处理请求映射的主要类就是HandlerMapping,而HandlerMappingIntrospector
类是HandlerMapping的集合工具类,springsecurity此处就是从spring容器中获取了该工具类,然后供自己内部使用(处理我们的自定义映射,后面具体讲解)。

 1public class MvcRequestMatcherProvider implements RequestMatcherProvider {
 2
 3    private final HandlerMappingIntrospector introspector;
 4
 5    public MvcRequestMatcherProvider(HandlerMappingIntrospector introspector) {
 6        this.introspector = introspector;
 7    }
 8
 9    @Override
10    public RequestMatcher getRequestMatcher(String pattern) {
11        return new MvcRequestMatcher(this.introspector, pattern);
12    }
13
14}

接下来就是SecurityFilterAutoConfiguration了:

SecurityFilterAutoConfiguration

 1@Configuration
 2@ConditionalOnWebApplication(type = Type.SERVLET)
 3@EnableConfigurationProperties(SecurityProperties.class)
 4@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
 5        SessionCreationPolicy.class })
 6@AutoConfigureAfter(SecurityAutoConfiguration.class)
 7public class SecurityFilterAutoConfiguration {
 8
 9    private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
10
11    @Bean
12    @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
13    public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
14            SecurityProperties securityProperties) {
15        DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
16                DEFAULT_FILTER_NAME);
17        registration.setOrder(securityProperties.getFilter().getOrder());
18        registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
19        return registration;
20    }
21
22    private EnumSet<DispatcherType> getDispatcherTypes(
23            SecurityProperties securityProperties) {
24        if (securityProperties.getFilter().getDispatcherTypes() == null) {
25            return null;
26        }
27        return securityProperties.getFilter().getDispatcherTypes().stream()
28                .map((type) -> DispatcherType.valueOf(type.name())).collect(Collectors
29                        .collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
30    }
31
32}

首先,我们发现这个类有一个@AutoConfigureAfter(SecurityAutoConfiguration.class),也就是说这个类要在我们讲的第一个SecurityAutoConfiguration才行(why? 别急),
然后它拿到我们一开始说的SecurityProperties,帮我们做了一个Filter:但是!这个filter具体是啥,它沒有直接告訴我們,只把它在spring中的bean的名字给出来了,springSecurityFilterChain
也就是説存在一个这样名字的springsecurity的filter,然后被spring代理了,管理它的生命周期。但是从名字我们大概可以猜出,不只是一个filter,是一个filter列表,既然这样,那我们直接在项目中搜索,看那个地方有这个名字的bean
最终在该地方找到:

springSecurityFilterChain

springSecurityFilterChain

发现该类是在WebSecurityConfiguration中初始化的,那WebSecurityConfiguration又是在哪来的呢,上面我们说到@EnableWebSecurity的时候,开启WebSecurityAdapter的配置,其实那个时候已经导入了(哈哈,上面我也没注意到),

springSecurityFilterChain

 1    private WebSecurity webSecurity;
 2
 3    private Boolean debugEnabled;
 4
 5    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
 6
 7    private ClassLoader beanClassLoader;
 8
 9    @Bean(name = "springSecurityFilterChain")
10    public Filter springSecurityFilterChain() throws Exception {
11        boolean hasConfigurers = webSecurityConfigurers != null
12                && !webSecurityConfigurers.isEmpty();
13        if (!hasConfigurers) {
14            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
15                    .postProcess(new WebSecurityConfigurerAdapter() {
16                    });
17            webSecurity.apply(adapter);
18        }
19        return webSecurity.build();
20    }

我们注意到这个初始化类有两个主要成员变量,WebSecurity和webSecurityConfigurers,而从这个springSecurityFilterChain方法我们可以看到该filter是通过构造器WebSecurity构造而来,
纳尼? 既然Websecurity构造了springSecurityFilterChain,那为什么下面还有一个 webSecurityConfigurers,并且是一个WebSecurity的list呢?
别急,我们来看下他们之间的关系。我们注意到还有这样一个方法:

 1@Autowired(required = false)
 2public void setFilterChainProxySecurityConfigurer(
 3        ObjectPostProcessor<Object> objectPostProcessor,
 4        @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
 5        throws Exception {
 6    webSecurity = objectPostProcessor
 7            .postProcess(new WebSecurity(objectPostProcessor));
 8    if (debugEnabled != null) {
 9        webSecurity.debug(debugEnabled);
10    }
11
12    Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
13
14    Integer previousOrder = null;
15    Object previousConfig = null;
16    for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
17        Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
18        if (previousOrder != null && previousOrder.equals(order)) {
19            throw new IllegalStateException(
20                    "@Order on WebSecurityConfigurers must be unique. Order of "
21                            + order + " was already used on " + previousConfig + ", so it cannot be used on "
22                            + config + " too.");
23        }
24        previousOrder = order;
25        previousConfig = config;
26    }
27    for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
28        webSecurity.apply(webSecurityConfigurer);
29    }
30    this.webSecurityConfigurers = webSecurityConfigurers;
31}

从这里我们就知道他们的关系了, 这个webSecurityConfigurers是通过spring注入进去的(尼玛,我都快整蒙圈了),他就代表那个过滤器链,也就是权限控制的关键,而我们一开始看到的Websecurity就是这个过滤器链的入口,由它来一个个的将
过过滤器链引用作为自己的成员变量,好了,他们之间的关系我们搞清楚了,接下来就又多了一个新问题,那个过滤器链又是在哪里给初始化了呢。

@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}")

这个el表达式的bean同样在这个配置类中:

1@Bean
2public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
3        ConfigurableListableBeanFactory beanFactory) {
4    return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
5}

那我们继续看这个AutowiredWebSecurityConfigurersIgnoreParents,它拿到了spring的容器beanFactory,然后得到了那个过滤器链,然后我还是太天真:

 1@SuppressWarnings({ "rawtypes", "unchecked" })
 2    public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
 3        List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
 4        Map<String, WebSecurityConfigurer> beansOfType = beanFactory
 5                .getBeansOfType(WebSecurityConfigurer.class);
 6        for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
 7            webSecurityConfigurers.add(entry.getValue());
 8        }
 9        return webSecurityConfigurers;
10    }

它居然不是直接初始化的,而是从beanFactory中取出来了所有WebSecurityConfigurer类型的bean,尼玛!那我们接着看实现了WebSecurityConfigurer并且作为bean在spring中已经初始化了类是哪一个,
不着不知道,依照吓一跳,查看类关系,居然又回到了最初的起点:

springSecurityFilterChain

springSecurityFilterChain


又是它!,我们继续查看他,终于!我们找到了那个过滤器链!

 1protected final HttpSecurity getHttp() throws Exception {
 2    if (http != null) {
 3        return http;
 4    }
 5
 6    DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
 7            .postProcess(new DefaultAuthenticationEventPublisher());
 8    localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
 9
10    AuthenticationManager authenticationManager = authenticationManager();
11    authenticationBuilder.parentAuthenticationManager(authenticationManager);
12    authenticationBuilder.authenticationEventPublisher(eventPublisher);
13    Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
14
15    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
16            sharedObjects);
17    if (!disableDefaults) {
18        // @formatter:off
19        http
20            .csrf().and()
21            .addFilter(new WebAsyncManagerIntegrationFilter())
22            .exceptionHandling().and()
23            .headers().and()
24            .sessionManagement().and()
25            .securityContext().and()
26            .requestCache().and()
27            .anonymous().and()
28            .servletApi().and()
29            .apply(new DefaultLoginPageConfigurer<>()).and()
30            .logout();
31        // @formatter:on
32        ClassLoader classLoader = this.context.getClassLoader();
33        List<AbstractHttpConfigurer> defaultHttpConfigurers =
34                SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
35
36        for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
37            http.apply(configurer);
38        }
39    }
40    configure(http);
41    return http;
42    }

由此方法我们得知,最终的过滤器链是保存在HttpSecuriry中,并且通过spring把所有AbstractHttpConfigurer子类都加入到容器中并且加入到了过滤器链中 http:apply(…)
那我们看下AbstractHttpConfigurer有哪些子类

springSecurityFilterChain

springSecurityFilterChain


:sob: 终于找到了,顺便看下HttpSecurity构成:

1public final class HttpSecurity extends
2        AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
3        implements SecurityBuilder<DefaultSecurityFilterChain>,
4        HttpSecurityBuilder<HttpSecurity> {
5    private final RequestMatcherConfigurer requestMatcherConfigurer;
6    private List<Filter> filters = new ArrayList<>();
7    private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
8    private FilterComparator comparator = new FilterComparator();

另外从上面那个方法中,我们还看到了一个很熟悉的过滤器:DefaultLoginPageConfigurer,我们查看它。

1private DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = new DefaultLoginPageGeneratingFilter();
2private DefaultLogoutPageGeneratingFilter logoutPageGeneratingFilter = new DefaultLogoutPageGeneratingFilter();

它有两个过滤器,登陆页面和注销页面,我们继续查看登陆页面,这个时候发现一点意外的收获:

  1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  2            throws IOException, ServletException {
  3        HttpServletRequest request = (HttpServletRequest) req;
  4        HttpServletResponse response = (HttpServletResponse) res;
  5
  6        boolean loginError = isErrorPage(request);
  7        boolean logoutSuccess = isLogoutSuccess(request);
  8        if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
  9            String loginPageHtml = generateLoginPageHtml(request, loginError,
 10                    logoutSuccess);
 11            response.setContentType("text/html;charset=UTF-8");
 12            response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
 13            response.getWriter().write(loginPageHtml);
 14
 15            return;
 16        }
 17
 18        chain.doFilter(request, response);
 19    }
 20
 21    private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
 22            boolean logoutSuccess) {
 23        String errorMsg = "Invalid credentials";
 24
 25        if (loginError) {
 26            HttpSession session = request.getSession(false);
 27
 28            if (session != null) {
 29                AuthenticationException ex = (AuthenticationException) session
 30                        .getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
 31                errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
 32            }
 33        }
 34
 35        StringBuilder sb = new StringBuilder();
 36
 37        sb.append("<!DOCTYPE html>\n"
 38                + "<html lang=\"en\">\n"
 39                + "  <head>\n"
 40                + "    <meta charset=\"utf-8\">\n"
 41                + "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
 42                + "    <meta name=\"description\" content=\"\">\n"
 43                + "    <meta name=\"author\" content=\"\">\n"
 44                + "    <title>Please sign in</title>\n"
 45                + "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
 46                + "    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
 47                + "  </head>\n"
 48                + "  <body>\n"
 49                + "     <div class=\"container\">\n");
 50
 51        String contextPath = request.getContextPath();
 52        if (this.formLoginEnabled) {
 53            sb.append("      <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"
 54                    + "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
 55                    + createError(loginError, errorMsg)
 56                    + createLogoutSuccess(logoutSuccess)
 57                    + "        <p>\n"
 58                    + "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
 59                    + "          <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
 60                    + "        </p>\n"
 61                    + "        <p>\n"
 62                    + "          <label for=\"password\" class=\"sr-only\">Password</label>\n"
 63                    + "          <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"
 64                    + "        </p>\n"
 65                    + createRememberMe(this.rememberMeParameter)
 66                    + renderHiddenInputs(request)
 67                    + "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
 68                    + "      </form>\n");
 69        }
 70
 71        if (openIdEnabled) {
 72            sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"
 73                    + "        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"
 74                    + createError(loginError, errorMsg)
 75                    + createLogoutSuccess(logoutSuccess)
 76                    + "        <p>\n"
 77                    + "          <label for=\"username\" class=\"sr-only\">Identity</label>\n"
 78                    + "          <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
 79                    + "        </p>\n"
 80                    + createRememberMe(this.openIDrememberMeParameter)
 81                    + renderHiddenInputs(request)
 82                    + "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
 83                    + "      </form>\n");
 84        }
 85
 86        if (oauth2LoginEnabled) {
 87            sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h3>");
 88            sb.append(createError(loginError, errorMsg));
 89            sb.append(createLogoutSuccess(logoutSuccess));
 90            sb.append("<table class=\"table table-striped\">\n");
 91            for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
 92                sb.append(" <tr><td>");
 93                String url = clientAuthenticationUrlToClientName.getKey();
 94                sb.append("<a href=\"").append(contextPath).append(url).append("\">");
 95                String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());
 96                sb.append(clientName);
 97                sb.append("</a>");
 98                sb.append("</td></tr>\n");
 99            }
100            sb.append("</table></div>\n");
101        }
102
103        sb.append("</body></html>");
104
105        return sb.toString();
106    }

我们的helloworld那一篇博客中的登陆页面即来源于此!(还有点惊喜哈:joy:),谈到这,突然记起我们上面谈了一个问题。
@AutoConfigureAfter(SecurityAutoConfiguration.class)为什么要用这个,现在应该知道了吧(:joy:),因为它要代理的filter在上一个注解。


然后我们继续回来看WebSecurity这个构造器(是不是都已经忘记我们是在说这个类的:joy:),这个类很长,我们直接看注释以及主要成员变量

 1/**
 2 * <p>
 3 * The {@link WebSecurity} is created by {@link WebSecurityConfiguration} to create the
 4 * {@link FilterChainProxy} known as the Spring Security Filter Chain
 5 * (springSecurityFilterChain). The springSecurityFilterChain is the {@link Filter} that
 6 * the {@link DelegatingFilterProxy} delegates to.
 7 * </p>
 8 *
 9 * <p>
10 * Customizations to the {@link WebSecurity} can be made by creating a
11 * {@link WebSecurityConfigurer} or more likely by overriding
12 * {@link WebSecurityConfigurerAdapter}.
13 * </p>
14 *
15 * @see EnableWebSecurity
16 * @see WebSecurityConfiguration
17 *
18 * @author Rob Winch
19 * @since 3.2
20 */
21private final Log logger = LogFactory.getLog(getClass());
22
23private final List<RequestMatcher> ignoredRequests = new ArrayList<>();
24
25private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();
26
27private IgnoredRequestConfigurer ignoredRequestRegistry;
28
29private FilterSecurityInterceptor filterSecurityInterceptor;
30
31private HttpFirewall httpFirewall;
32
33private boolean debugEnabled;
34
35private WebInvocationPrivilegeEvaluator privilegeEvaluator;
36
37private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
38
39private SecurityExpressionHandler<FilterInvocation> expressionHandler = defaultWebSecurityExpressionHandler;
40
41private Runnable postBuildAction = new Runnable() {
42    public void run() {
43    }
44};

大概意思就是说这个类是被专门用来创建FilterChainProxy,即我们所知道的(springSecurityFilterChain),然后它的配置均来自于
WebSecurityConfigurer,默认实现是WebSecurityConfigurerAdapter,这是它第N次出现了!
接下来我们研究下它的主要成员变量,List ignoredRequests = new ArrayList<>();一个匹配请求url的处理器,这处的作用是用来存储我们要忽略的url(不走springsecurity的过滤器链),
FilterSecurityInterceptor,过滤器链就是由它来调用的,HttpFirewall,看名字就知道起到了额外的配置作用(事实上初始化是一个空对象)。
securityFilterChainBuilders可以看成是WebSecurity内部过滤器链的引用。
defaultWebSecurityExpressionHandler是springsecurity el表达式处理器(后面讲解注解时我们再来回顾),比如说 hasAnyAuthority(…),就可以由它来处理
另外还有一个 WebInvocationPrivilegeEvaluator,它叫做权限计算器,其实就是和防火墙一样,多了一层判断,它的默认实现是

public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {

表示所有用户都由权限(因为是默认的)

最后,还有最后一个配置类UserDetailsServiceAutoConfiguration

UserDetailsServiceAutoConfiguration

 1@Configuration
 2@ConditionalOnClass(AuthenticationManager.class)
 3@ConditionalOnBean(ObjectPostProcessor.class)
 4@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class,
 5        UserDetailsService.class })
 6public class UserDetailsServiceAutoConfiguration {
 7
 8    private static final String NOOP_PASSWORD_PREFIX = "{noop}";
 9
10    private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern
11            .compile("^\\{.+}.*$");
12
13    private static final Log logger = LogFactory
14            .getLog(UserDetailsServiceAutoConfiguration.class);
15
16    @Bean
17    @ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
18    @Lazy
19    public InMemoryUserDetailsManager inMemoryUserDetailsManager(
20            SecurityProperties properties,
21            ObjectProvider<PasswordEncoder> passwordEncoder) {
22        SecurityProperties.User user = properties.getUser();
23        List<String> roles = user.getRoles();
24        return new InMemoryUserDetailsManager(User.withUsername(user.getName())
25                .password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
26                .roles(StringUtils.toStringArray(roles)).build());
27    }
28
29    private String getOrDeducePassword(SecurityProperties.User user,
30            PasswordEncoder encoder) {
31        String password = user.getPassword();
32        if (user.isPasswordGenerated()) {
33            logger.info(String.format("%n%nUsing generated security password: %s%n",
34                    user.getPassword()));
35        }
36        if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
37            return password;
38        }
39        return NOOP_PASSWORD_PREFIX + password;
40    }
41
42}

这次这个配置很简单,因为我们没有配置oauth2,所以它帮我们做了一个UserDetails,并且是根据我们配置的用户密码,把他们load到内存(因为没有db),以后的权限判断就根据
userDetails来判断了,由此可知,如果我们要扩展,实现该类也是必然的。

总结

这次,我们从源码的角度查看了springboot帮我们做的配置(还真是绕了不少圈子),并且只得到了login页面的来源,接下来,我们就探究下springsecurity的具体配置!

本文原创地址,我的博客:https://jsbintask.cn/2019/01/08/springsecurity-configsourcecode/  未经允许,禁止转载!

系列其它文章
[springboot整合springsecurity从Hello World到源码解析(一):hello world程序入门](https://blog.csdn.net/Iperishing/article/details/86504847)
[springboot整合springsecurity从Hello World到源码解析(二):springsecurity配置加载解析](https://blog.csdn.net/Iperishing/article/details/86505017)
[springboot整合springsecurity从Hello World到源码解析(三):基础配置详解](https://blog.csdn.net/Iperishing/article/details/86505120)
[springboot整合springsecurity从Hello World到源码解析(四):springsecurity基础架构解析](https://blog.csdn.net/Iperishing/article/details/86521621)
[springboot整合springsecurity从Hello World到源码解析(五):springsecurity+jwt整合restful服务](https://blog.csdn.net/Iperishing/article/details/86575416)

猜你喜欢

转载自blog.csdn.net/Iperishing/article/details/86505017