4

Spring セキュリティを提供したい Spring ベースの Web サービスがあります。動作しており、USER および ADMIN ロールを介して認証できること。ただし、ユーザーおよび管理者の役割ではなく、要求の送信元のサブドメインを使用して要求を認証する必要があるという新しい要件があります。

通常、IP による認証があります。

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

ただし、私の場合はまったく異なります。リクエストの送信元のドメインとサブドメインに基づいて認証する必要があります。

お気に入り:

jim.foo.com 
tim.foo.com

jim.foo.com と tim.foo.com の IP アドレスは同じです。また、各サブドメインは個別に認証されます。

出来ますか?

4

2 に答える 2

9

@zayagiの回答へのコメントで@franz-ebnerが要求したように、ここで具体的な完全な例を挙げることができます。@zayagiの答えは完璧な答えです-これは、特定のユースケースで他の人を助けるためのものです。

この例は、Spring Boot が Spring 4.0.x で 1.1.6 だったときに Java 8 で作成されました (Spring ブートは現在、Web 構成の改善を含む Spring 4.2 で 1.3.0 です)。これは 2014 年末に書かれたもので、更新が必要かもしれませんが、リクエストされたのでそのまま提供して、他の人を助けることができるかもしれません。共有したくない特定のIPアドレスが含まれているため、テストを提供できません;-)

最初のクラスは、Spring セキュリティ式 (isCompanyInternal() など) を定義し、x-forwarded-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();
        }

    }

2 番目のクラスは、セキュリティを構成するときに挿入する式ハンドラーを定義します。

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

最後に、これらを一緒に使用する例を次に示します。 @Configuration public static class WebInternalSecurityConfig extends 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()");
        }
    }
于 2015-11-30T21:12:01.030 に答える
4

SecurityExpressionRootおよびそのサブクラスで定義されている組み込み関数を超えて、独自の関数を定義することができますWebSecurityExpressionRoot。後者を拡張し、好きな方法でオブジェクトを特定する独自の関数を追加しrequest、デフォルトのもの ( ) の代わりにそれを使用するように Spring Security を構成するだけで済みますWebSecurityExpressionRoot。方法は次のとおりです。

  1. カスタム関数を含む独自の実装をDefaultWebSecurityExpressionHandler.createSecurityExpressionRoot()構築するサブクラスでオーバーライドします。SecurityExpressionRoot
  2. このカスタム ハンドラーの Bean を作成し、 config 要素<expression-handler ref="yourCustomSecurityExpressionRootHandler">内でそれへの参照を作成します。<http>
于 2013-04-19T12:21:04.050 に答える