Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

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):

    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: /info and /health are unauthorized, while /warehouses needs authentication, and everything else also needs authentication (returns 401, but when invoking with authentication, it returns 403).

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

  • 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).

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

What can I do here?

like image 661
Paŭlo Ebermann Avatar asked Mar 02 '16 18:03

Paŭlo Ebermann


People also ask

What can I use instead of WebSecurityConfigurerAdapter?

You need to declare SecurityFilterChain and WebSecurityCustomizer beans instead of overriding methods of WebSecurityConfigurerAdapter class. NOTE: If you don't want to change your current code, you should keep Spring Boot version lower than 2.7. 0 or Spring Security version older than 5.7. 1.

Can we have two WebSecurityConfigurerAdapter?

When using Java configuration, the way to define multiple security realms is to have multiple @Configuration classes that extend the WebSecurityConfigurerAdapter base class – each with its own security configuration. These classes can be static and placed inside the main config.


1 Answers

I found a solution:

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

/**
 * 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.)

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

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.

You could use this

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

to make this port fully unauthorized.

like image 117
Paŭlo Ebermann Avatar answered Oct 29 '22 01:10

Paŭlo Ebermann