spring security

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring security相关的知识,希望对你有一定的参考价值。

这个需要从DelegatingFilterProxy类说起,从名字来看是个代理类,也就是说它并不是filter的实际实现,而且他从属于 org.springframework.web.filter这个包,也就是说它没有和springsecurity强绑定,其实从他的dofilter方法不难发现它是通过bean名去springcontext中取出相应的filter bean,然后执行dofilter。为啥不直接写成filter呢,可能设计者就是想设计的灵活吧,根据配置的bean名从springcontext取出。

DelegatingFilterProxy需要在web.xml里配置,和其他filter没有区别

<!-- SpringSecurity需要的filter --><filter>
    <filter-name>spring-security</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>abcFilterChain</param-value>
    </init-param></filter><filter-mapping>
    <filter-name>spring-security</filter-name>
    <url-pattern>/*</url-pattern></filter-mapping>

这里init-param里的param-name和param-value可以不用配置,只要配置filter-name为bean名也可以,程序中找不到param-name参数就会使用filter-name,一般我们使用springsecurity时,直接将filter-name写为springSecurityFilterChain,因为springsecurity在解析spring-security.xml时会默认注册springSecurityFilterChain这个bean。这个得从标签解析说起,Spring Security的标签解析由org.springframework.security.config.SecurityNamespaceHandler来处理。该类实现接口:NamespaceHandler,Spring中自定义标签都要实现该接口,这个接口有三个方法init、parse、decorate,其中init用于自定义标签的初始化,parse用于解析标签,decorate用于装饰。SecurityNamespaceHandler类的init方法完成了标签解析类的注册工作。打开SecurityNamespaceHandler源码。

public void init() {
    loadParsers();}

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

上面可以看出SecurityNamespaceHandler类的init方法完成了标签解析类的注册工作,并且http的标签解析类注册代码为:

parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());


在该类中parse方法

public BeanDefinition parse(Element element, ParserContext pc) {
    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
    pc.pushContainingComponent(compositeDef);
    // 这里创建了listFactoryBean实例和springSecurityFilterChain实例,详解移步3.2.1
    registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
    // 获取listFactoryBean实例
    BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS);
    List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();
    // 创建过滤器链代码,注意这里添加的filter会在FilterChainProxy的doFilterInternal中使用。
    filterChains.add(createFilterChain(element, pc));
    pc.popAndRegisterContainingComponent();
    return null;}

首先看registerFilterChainProxyIfNecessary方法

static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
    // 判断是否已经注册了FILTER_CHAIN_PROXY,若已经注册则直接返回,也就是说用户可以自定义BeanIds.FILTER_CHAIN_PROXY bean,如果那样的话,系统就不自动
    注册那个bean了
    if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
        return;
    }
    // 注册ListFactoryBean,也就是把BeanIds.FILTER_CHAINS和一个beandefinition绑定,springcontext里都是这样保持一个map,然后调用getbean时会根据
    bean名找到beandefinition,然后根据beanDefinition创建bean实例,当然如果web.xml里配置了ContextLoaderListener,他会在tomcat启动后初始化springcontext
    并创建单例的bean,这个具体参见ContextLoaderListener的分析。
    BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);
    listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());
    pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, BeanIds.FILTER_CHAINS));

    // 注册FilterChainProxy
    BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
    fcpBldr.getRawBeanDefinition().setSource(source);
    fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);//这里将BeanIds.FILTER_CHAINS设置为FilterChainProxy类的初始化参数
    //查看FilterChainProxy类的doFilterInternal->getFilters会用到这个参数,也就是filterchains,之后会执行这个chain里的filter,
    //那么这个chain里的filter从哪注册呢,可以看看上面parse方法里的倒数第三句。
    fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(DefaultFilterChainValidator.class));
    BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
    pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
    // 此处为FILTER_CHAIN_PROXY取别名为springSecurityFilterChain
    pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);}

接着看createFilterChain

    // 创建过滤器链的代码
private BeanReference createFilterChain(Element element, ParserContext pc) {
    // 判断是否需要SpringSecurity拦截
    boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED));
    if (!secured) {
        // 如果没pattern并且配置request-matcher-ref为空 添加错误信息
        if (!StringUtils.hasText(element.getAttribute(ATT_PATH_PATTERN)) && !StringUtils.hasText(ATT_REQUEST_MATCHER_REF)) {
            pc.getReaderContext().error("The '" + ATT_SECURED + "' attribute must be used in combination with" + " the '" + ATT_PATH_PATTERN + "' or '" + ATT_REQUEST_MATCHER_REF + "' attributes.", pc.extractSource(element));
        }
        for (int n = 0; n < element.getChildNodes().getLength(); n++) {
            if (element.getChildNodes().item(n) instanceof Element) {
                // 如果element有子节点并且instanceofElement 添加错误信息
                pc.getReaderContext().error("If you are using <http> to define an unsecured pattern, " + "it cannot contain child elements.", pc.extractSource(element));
            }
        }

        return createSecurityFilterChainBean(element, pc, Collections.emptyList());
    }

    final BeanReference portMapper = createPortMapper(element, pc);
    final BeanReference portResolver = createPortResolver(portMapper, pc);

    ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
    BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders);

    boolean forceAutoConfig = isDefaultHttpConfig(element);
    // HttpConfigurationBuilder创建过滤器链,详情移步3.2.2.1
    HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper, portResolver, authenticationManager);
    // AuthenticationConfigBuilder创建过滤器链,详情移步3.2.2.2
    AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager, httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());

    httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
    httpBldr.setEntryPoint(authBldr.getEntryPointBean());
    httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());

    authenticationProviders.addAll(authBldr.getProviders());

    List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();

    // 添加过滤器到unorderedFilterChain
    unorderedFilterChain.addAll(httpBldr.getFilters());
    unorderedFilterChain.addAll(authBldr.getFilters());
    unorderedFilterChain.addAll(buildCustomFilterList(element, pc));

    // 对过滤器链进行排序,详细分析请移步3.2.2.3
    Collections.sort(unorderedFilterChain, new OrderComparator());
    // 对过滤器链正确性检查
    checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));

    // The list of filter beans
    List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();

    for (OrderDecorator od : unorderedFilterChain) {
        filterChain.add(od.bean);
    }
    // 创建过滤器链,详情移步3.2.2.4
    return createSecurityFilterChainBean(element, pc, filterChain);}


以上是关于spring security的主要内容,如果未能解决你的问题,请参考以下文章

oauth2 spring-security 如果您在请求令牌或代码之前登录

Spring Security问题

Spring Security:如何获取自动渲染的登录页面代码?

springboot集成spring security实现restful风格的登录认证 附代码

spring security 匿名访问安全吗

如何使用 Spring Security @Secured 注解测试 Spring Boot @WebFluxTest