Spring Security-不是通过IP而是通过域/子域进行身份验证?

xyb*_*rek 4 java spring spring-security

我有一个要提供Spring安全性的基于Spring的Web服务。它可以正常工作,并且可以通过USER和ADMIN角色进行身份验证。但是,我有一个新的要求,即我需要对请求的身份进行验证,而不是USER和ADMIN角色,而是使用该请求来自的子域。

通常,通过IP进行身份验证:

 <http use-expressions="true">
    <intercept-url pattern="/admin*"
        access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
    ...
  </http>
Run Code Online (Sandbox Code Playgroud)

但是,我的情况大不相同,我需要根据请求来自的域和子域进行身份验证。

喜欢:

jim.foo.com 
tim.foo.com
Run Code Online (Sandbox Code Playgroud)

其中jim.foo.com和tim.foo.com具有相同的IP地址。并且每个子域都分别进行身份验证。

可能吗?

Mat*_*rne 8

根据@ franz-ebner在对@zayagi答案的评论中的要求,我可以在此处给出一个具体的完整示例。@zayagi的答案是一个完美的答案-这只是为了帮助其他人使用特定的用例。

这个示例是用Java 8编写的,当时Spring Boot在Spring 4.0.x上为1.1.6(Spring Boot现在在Spring 4.2上为1.3.0,其中包含Web配置的改进)。该文档于2014年底编写,可能需要刷新,但自从提出要求以来就可以按原样提供,并且可能能够帮助其他人。我无法提供测试,因为它们中有我不想共享的特定IP地址;-)

第一类定义Spring安全性表达式(例如isCompanyInternal()),并包括对x-forward-for标头的支持。使用此标头,您不应该在所有情况下都信任它,因为任何人都可以添加它,并且可能构成安全威胁。因此,此标头仅信任某些内部ip范围。

    package org.mycompany.spring.security.web.acccess.expression;

    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.expression.WebSecurityExpressionRoot;
    import org.springframework.security.web.util.matcher.IpAddressMatcher;
    import org.springframework.util.Assert;

    import java.util.Optional;
    import java.util.stream.Stream;

    public class MyCompanyWebSecurityExpressionRoot extends WebSecurityExpressionRoot {
        public static final String LOCALHOST = "127.0.0.1";

        public static final String COMPANY_DESKTOPS = "-suppressed for example-";
        public static final String COMPANY_INTERNET_1 = "-suppressed for example-";
        public static final String COMPANY_INTERNET_2 = "-suppressed for example-";

        /**
         * See http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
         */
        public static final String RFC_1918_INTERNAL_A = "10.0.0.0/8";
        /**
         * See http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
         */
        public static final String RFC_1918_INTERNAL_B = "172.16.0.0/12";
        /**
         * See http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
         */
        public static final String RFC_1918_INTERNAL_C = "192.168.0.0/16";


        private IpAddressMatcher[] internalIpMatchers;
        private IpAddressMatcher trustedProxyMatcher;

        public MyCompanyWebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {
            super(a, fi);
            setInternalIpRanges(RFC_1918_INTERNAL_A,
                    COMPANY_INTERNET_1,
                    COMPANY_INTERNET_2,
                    COMPANY_DESKTOPS,
                    RFC_1918_INTERNAL_B,
                    RFC_1918_INTERNAL_C,
                    LOCALHOST);
            setTrustedProxyIpRange(RFC_1918_INTERNAL_A);
        }

        public boolean hasAnyIpAddress(String... ipAddresses) {
            return Stream.of(ipAddresses)
                    .anyMatch(ipAddress -> new IpAddressMatcher(ipAddress).matches(request));
        }

        public boolean hasAnyIpAddressBehindProxy(String trustedProxyRange, String... ipAddresses) {
            String remoteIpAddress = getForwardedIp(trustedProxyRange).orElseGet(request::getRemoteAddr);
            return Stream.of(ipAddresses)
                            .anyMatch(ipAddress -> new IpAddressMatcher(ipAddress).matches(remoteIpAddress));
        }

        public boolean isCompanyInternal() {
            String remoteIpAddress = getForwardedIp(trustedProxyMatcher).orElseGet(request::getRemoteAddr);
            return Stream.of(internalIpMatchers)
                    .anyMatch(matcher -> matcher.matches(remoteIpAddress));
        }

        /**
         * <p>This specifies one or more IP addresses/ranges that indicate the remote client is from the company network.</p>
         *
         * <p>If not set, this will default to all of the following values:</p>
         *     <ul>
         *         <li>{@code RFC_1918_INTERNAL_A}</li>
         *         <li>{@code RFC_1918_INTERNAL_B}</li>
         *         <li>{@code RFC_1918_INTERNAL_C}</li>
         *         <li>{@code COMPANY_INTERNET_1}</li>
         *         <li>{@code COMPANY_INTERNET_2}</li>
         *         <li>{@code COMPANY_DESKTOPS}</li>
         *         <li>{@code LOCALHOST}</li>
         *     </ul>
         *
         * @param  internalIpRanges ip addresses or ranges. Must not be empty.
         *
         */
        public void setInternalIpRanges(String... internalIpRanges) {
            Assert.notEmpty(internalIpRanges, "At least one IP address/range is required");
            this.internalIpMatchers = Stream.of(internalIpRanges)
                    .map(IpAddressMatcher::new)
                    .toArray(IpAddressMatcher[]::new);
        }

        /**
         * <p>When checking for the <code>x-forwarded-for</code> header in the incoming request we will only use
         * that value from a trusted proxy as it can be spoofed by anyone. This value represents the IP address
         * or IP range that we will trust.</p>
         *
         * <p>The default value if this is not set is {@code RFC_1918_INTERNAL_A}.</p>
         *
         * @param  trustedProxyIpRange ip address or range. Must not be null.
         *
         */
        public void setTrustedProxyIpRange(String trustedProxyIpRange) {
            Assert.notNull(trustedProxyIpRange, "A non-null value is for trusted proxy IP address/range");
            this.trustedProxyMatcher = new IpAddressMatcher(trustedProxyIpRange);
        }

        private Optional<String> getForwardedIp(String trustedProxyRange) {
            return getForwardedIp(new IpAddressMatcher(trustedProxyRange));
        }

        private Optional<String> getForwardedIp(IpAddressMatcher trustedProxyMatcher) {
            String proxiedIp = request.getHeader("x-forwarded-for");
            if (proxiedIp != null && trustedProxyMatcher.matches(request.getRemoteAddr())) {
                return Optional.of(proxiedIp);
            }
            return Optional.empty();
        }

    }
Run Code Online (Sandbox Code Playgroud)

第二类定义在配置安全性时注入的表达式处理程序。

    package org.mycompany.spring.security.web.acccess.expression;

    import org.springframework.security.access.expression.SecurityExpressionOperations;
    import org.springframework.security.authentication.AuthenticationTrustResolver;
    import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.FilterInvocation;
    import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;

    public class MyCompanyWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {
        private static final AuthenticationTrustResolver TRUST_RESOLVER = new AuthenticationTrustResolverImpl();

        private String[] customInternalIpRanges;
        private String customTrustedProxyIpRange;


        @Override
        protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
            MyCompanyWebSecurityExpressionRoot root = new MyCompanyWebSecurityExpressionRoot(authentication, fi);
            root.setPermissionEvaluator(getPermissionEvaluator());
            root.setTrustResolver(TRUST_RESOLVER);
            root.setRoleHierarchy(getRoleHierarchy());

            if (customInternalIpRanges != null) {
                root.setInternalIpRanges(customInternalIpRanges);
            }
            if (customTrustedProxyIpRange != null) {
                root.setTrustedProxyIpRange(customTrustedProxyIpRange);
            }

            return root;
        }

        /**
         * <p>Only set this if you want to override the default internal IP ranges defined within
         * {@link MyCompanyWebSecurityExpressionRoot}.</p>
         *
         * <p>See {@link MyCompanyWebSecurityExpressionRoot#setInternalIpRanges(String...)}</p>
         *
         * @param customInternalIpRanges ip address or ranges
         */
        public void setCustomInternalIpRanges(String... customInternalIpRanges) {
            this.customInternalIpRanges = customInternalIpRanges;
        }

        /**
         * Only set this if you want to override the default trusted proxy IP range set in
         * {@link MyCompanyWebSecurityExpressionRoot}.
         *
         * @param customTrustedProxyIpRange ip address or range
         */
        public void setCustomTrustedProxyIpRange(String customTrustedProxyIpRange) {
            this.customTrustedProxyIpRange = customTrustedProxyIpRange;
        }
    }
Run Code Online (Sandbox Code Playgroud)

最后是结合使用这些示例的示例:@Configuration公共静态类WebInternalSecurityConfig扩展了WebSecurityConfigurerAdapter {

        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()
                    .antMatchers("/favicon.ico", "/robots.txt");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .authorizeRequests()
                .expressionHandler(new MyCompanyWebSecurityExpressionHandler())
                    .anyRequest().access("isCompanyInternal()");
        }
    }
Run Code Online (Sandbox Code Playgroud)


zag*_*gyi 5

SecurityExpressionRoot除了及其子类中定义的内置函数之外,还可以定义自己的函数WebSecurityExpressionRoot。您只需要扩展后者,添加您自己的函数(不按照request您喜欢的方式查看对象),然后配置 Spring Security 以使用它而不是默认函数 ( WebSecurityExpressionRoot)。具体方法如下:

  1. DefaultWebSecurityExpressionHandler.createSecurityExpressionRoot()在构造您自己的包含自定义函数的实现的子类中进行重写SecurityExpressionRoot
  2. <expression-handler ref="yourCustomSecurityExpressionRootHandler">创建此自定义处理程序的 bean,并在 config 元素中对其进行引用<http>

  • 这在很大程度上取决于设置。通常该值取决于负载均衡器或代理服务器发送的标头。例如,Tomcat 的 [RemoteIPValve](http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html) 允许您配置所使用的标头。例如,如果您允许外部客户端设置/伪造“x-forwarded-for”标头,您将获得不准确的值。如果您将其设置为您控制的标头,并且您的代理根据您认为是外部的内容进行设置,那么您就知道它是准确的。 (2认同)