java 如何告诉 Spring Security 仅针对特定端口应用 authorizeRequests?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35755716/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 00:32:55  来源:igfitidea点击:

How can I tell spring security to apply authorizeRequests just for a specific port?

javaspringspring-securityport

提问by Pa?lo Ebermann

We configured our new micro service (using Spring-Boot) in a way that the official API is on port 8080 (which is be mapped outside of our virtual network to normal HTTPS on port 443), while some management functions are on a secondary HTTP port 7979. These are only used inside the virtual network, and used for monitoring, loadbalancing etc.

我们配置了新的微服务(使用 Spring-Boot),官方 API 位于 8080 端口(在我们的虚拟网络之外映射到端口 443 上的普通 HTTPS),而一些管理功能在辅助 HTTP 上端口 7979。这些仅在虚拟网络内部使用,用于监控、负载平衡等。

All API access needs to be secured with OAuth, while the management functions should be accessible freely inside the network. So we configured Spring security this way (http is a HttpSecurity object):

所有 API 访问都需要使用 OAuth 进行保护,而管理功能应该可以在网络内部自由访问。所以我们这样配置Spring security(http是一个HttpSecurity对象):

    http
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
        .and()
        .authorizeRequests()
            .antMatchers("/info").anonymous()
            .antMatchers("/health").anonymous()

            .antMatchers(HttpMethod.GET, "/warehouses/**").access(oauthScopeRead)
            .antMatchers(HttpMethod.PUT, "/warehouses/**").access(oauthScopeWrite)

            .anyRequest().denyAll();

This has the effect on both ports: /infoand /healthare unauthorized, while /warehousesneeds authentication, and everything else also needs authentication (returns 401, but when invoking with authentication, it returns 403).

这对两个端口都有影响:/info并且/health是未授权的,同时/warehouses需要身份验证,其他所有端口也需要身份验证(返回 401,但在使用身份验证调用时,它返回 403)。

As there is no /infoor /healthon the public port, these return 404 for unauthorized users, while everything else returns 401. I'm unsatisfied with this and would like to have

由于没有/info/health在公共端口上,这些为未经授权的用户返回 404,而其他一切都返回 401。我对此不满意,希望有

  • on the public port, require authentication for everything (and return 404 or 403 only after being authenticated)
  • on the admin port, require no authentication at all (return 404 for everything which is not one of the configured endpoints).
  • 在公共端口上,要求对所有内容进行身份验证(并且只有在通过身份验证后才返回 404 或 403)
  • 在管理端口上,根本不需要身份验证(对于不是已配置端点之一的所有内容都返回 404)。

I couldn't find anything about ports in the Spring Security Javadocs or reference documentation.

我在 Spring Security Javadocs 或参考文档中找不到任何关于端口的信息

What can I do here?

我可以在这里做什么?

回答by Pa?lo Ebermann

I found a solution:

我找到了一个解决方案:

The authorizeRequests()method here returns an ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry, which has (from its ancestor class AbstractRequestMatcherRegistry) beside some antMatchersmethods also a generic requestMatchers()method, which takes one or more RequestMatcherobjects. It turns out this is an interface I can implement myself:

authorizeRequests()这里的方法返回一个ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry,它有(来自它的祖先类 AbstractRequestMatcherRegistry)除了一些antMatchers方法之外还有一个通用requestMatchers()方法,它接受一个或多个RequestMatcher对象。事实证明这是一个我可以自己实现的接口:

/**
 * A request matcher which matches just a port.
 *
 * @param   port  the port to match.
 *
 * @return  the new matcher.
 */
private RequestMatcher forPort(final int port) {
    return (HttpServletRequest request) -> port == request.getLocalPort();
}

(This is Java 8 syntax, with previous Java versions you'll had to write an anyonymous class here.)

(这是 Java 8 语法,对于以前的 Java 版本,您必须在这里编写一个匿名类。)

While requestMatcherstakes several such matchers, it looks like those are connected by OR (at least this example suggeststhis), thus I used an AndRequestMatcher to connect it to the matcher for the path (and HTTP method)).

虽然requestMatchers需要几个这样的匹配器,但看起来它们是通过 OR 连接的(至少这个例子表明了这一点),因此我使用了一个 AndRequestMatcher 将它连接到路径的匹配器(和 HTTP 方法)。

The final code looked like this:

最终代码如下所示:

@Value("${management.port}")
private int managementPort;

@Value("${server.port}")
private int apiPort;

/**
 * Configure scopes for specific controller/httpmethods/roles here.
 */
@Override
public void configure(final HttpSecurity http) throws Exception {
    //J-
    http
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
        .and()
        .authorizeRequests()
            .requestMatchers(forPortAndPath(managementPort, "/info")).anonymous()
            .requestMatchers(forPortAndPath(managementPort, "/health")).anonymous()

            .requestMatchers(forPortAndPath(apiPort, HttpMethod.GET, "/warehouses/**")).access(oauthScopeRead)
            .requestMatchers(forPortAndPath(apiPort, HttpMethod.PUT, "/warehouses/**")).access(oauthScopeWrite)

            .anyRequest().denyAll();
    //J+
}

/**
 * Creates a request matcher which only matches requests for a specific local port and path (using an
 * {@link AntPathRequestMatcher} for the path part).
 *
 * @param   port         the port to match
 * @param   pathPattern  the pattern for the path.
 *
 * @return  the new request matcher.
 */
private RequestMatcher forPortAndPath(final int port, @Nonnull final String pathPattern) {
    return new AndRequestMatcher(forPort(port), new AntPathRequestMatcher(pathPattern));
}

/**
 * Creates a request matcher which only matches requests for a specific local port, path and request method (using
 * an {@link AntPathRequestMatcher} for the path part).
 *
 * @param   port         the port to match
 * @param   pathPattern  the pattern for the path.
 * @param   method       the HttpMethod to match. Requests for other methods will not be matched.
 *
 * @return  the new request matcher.
 */
private RequestMatcher forPortAndPath(final int port, @Nonnull final HttpMethod method,
        @Nonnull final String pathPattern) {
    return new AndRequestMatcher(forPort(port), new AntPathRequestMatcher(pathPattern, method.name()));
}

/**
 * A request matcher which matches just a port.
 *
 * @param   port  the port to match.
 *
 * @return  the new matcher.
 */
private RequestMatcher forPort(final int port) {
    return (HttpServletRequest request) -> { return port == request.getLocalPort(); };
}

This does not fully reflect the question: the managementPort has here just "/info" and "/health" public reachable, instead of everything.

这并没有完全反映问题: managementPort 在这里只有“/info”和“/health”公共可访问,而不是所有内容。

You could use this

你可以用这个

                 .requestMatchers(forPort(managementPort)).anonymous()

to make this port fully unauthorized.

使这个端口完全未经授权。

回答by Adrian Baker

A straightforward way to permit all access to management endpoints, regardless of which port they are running on:

一种允许对管理端点进行所有访问的直接方法,无论它们在哪个端口上运行:

http.authorizeRequests()
    .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll();

The org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest#toAnyEndpointmethod returns a matcher that only matches actuator requests.

org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest#toAnyEndpoint方法返回一个匹配,只有执行的请求相匹配。

See spring-boot-samplesfor a more complete example.

有关更完整的示例,请参阅spring-boot-samples

回答by MrBit Fly

Based in the previous answer I develop this class that excludes the admin port from the security. I hope this helps:

基于上一个答案,我开发了这个类,从安全性中排除了管理端口。我希望这有帮助:

@EnableWebSecurity
@Configuration
public class WebSecurityAdapter extends WebSecurityConfigurerAdapter {
  /**
   * Puerto de administración por donde escucha Actuator.
   */
  @Value("${management.server.port}")
  private int managementPort;
  /*
   * (non-Javadoc)
   * 
   * @see org.springframework.security.config.annotation.web.configuration.
   * WebSecurityConfigurerAdapter#configure(org.springframework.security.config.
   * annotation.web.builders.WebSecurity)
   */
  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().requestMatchers(forPort(managementPort));
  }
  /**
   * @param port
   *          Puerto que se desea comparar con el puerto de la respuesta http.
   * @return Verdadero si el puerto de la respuesta http es igual al puerto
   *         especificado.
   */
  private RequestMatcher forPort(int port) {
    return (HttpServletRequest request) -> {
      return port == request.getLocalPort();
    };
  }
}